Java序列化进阶:Java内置序列化的三种方式

Java序列化就是把Java对象按照一定的格式存到文件或者磁盘当中

序列化的进阶:即三种方式,任何一种方式都可以进行序列化和反序列化

如果将数据读写到文档,

一般通过 ObjectOutputStream 将数据写入到文件当中,就是一种序列化的过程;

通过 ObjectInputStream 将数据从文件中读出来,就是一种反序列化的过程

如果对数据不进行序列化系统就会抛出异常信息:java.io.NotSerializableException

查看ObjectOutputStream 的 writeObject0() 方法源码:

// 判断对象是否为字符串类型,如果是,则调用 writeString 方法进行序列化
if (obj instanceof String) {
    writeString((String) obj, unshared);
}
// 判断对象是否为数组类型,如果是,则调用 writeArray 方法进行序列化
else if (cl.isArray()) {
    writeArray(obj, desc, unshared);
}
// 判断对象是否为枚举类型,如果是,则调用 writeEnum 方法进行序列化
else if (obj instanceof Enum) {
    writeEnum((Enum<?>) obj, desc, unshared);
}
// 判断对象是否为可序列化类型,如果是,则调用 writeOrdinaryObject 方法进行序列化
else if (obj instanceof Serializable) {
    writeOrdinaryObject(obj, desc, unshared);
}
// 如果对象不能被序列化,则抛出 NotSerializableException 异常
else {
if (extendedDebugInfo) {
    throw new NotSerializableException(
        cl.getName() + "\n" + debugInfoStack.toString());
} else {
    throw new NotSerializableException(cl.getName());
}
}

ObjectOutputStream 在序列化的时候,会判断被序列化的对象是哪一种类型,字符串?数组?枚举?还是 Serializable,如果全都不是的话,抛出 NotSerializableException

1、实现Serializable接口

使用默认的序列化机制,即实现Serializable接口即可,不需要实现任何方法。

该接口没有任何方法,只是一个标记而已,告诉Java虚拟机该类可以被序列化了。然后利用ObjectOutputStream进行序列化和用ObjectInputStream进行反序列化。

注意:

该方式下序列化机制会自动保存该对象的成员变量 ,static成员变量和transient关键字修饰的成员变量不会被序列化保存。如:

import java.io.Serializable;
import java.util.List;

public class SerializeTest implements Serializable {
    private static final long serialVersionUID = 213424233;
    private String name;
    private static String teacher;
    private final int bornYear;
    private static final String mother="mother";
    private transient int age;
    private transient List<String> friends;
    SerializeTest(){
        bornYear  = 2010;
    }
@Override
    public String toString() {
        return "SerializeTest{" +
                "name='" + name + '\'' +
                ", bornYear=" + bornYear +
                ", teacher=" + teacher +
                ", age=" + age +
                ", friends=" + friends +
                '}';
    }
}

要序列化的对象

看看序列化和反序列化效果:

 SerializeTest test = new SerializeTest();
        test.setName("myName");
        test.setAge(18);
        List<String> friends = new ArrayList<String>();
        friends.add("friendOne");
        friends.add("friendTwo");
        test.setFriends(friends);
        test.setTeacher("myTeacher");

        try {
            //序列化到文件
            ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(new File("D:\\user\\serialTest")));
            oos.writeObject(test);
            System.out.println("序列化:"+test.toString());
            test.setTeacher("myTeacher2");
            //从文件反序列化
            ObjectInputStream ois = new ObjectInputStream(new FileInputStream(new File("D:\\user\\serialTest")));
            SerializeTest objRead = (SerializeTest) ois.readObject();
            System.out.println("序列化后:"+objRead.toString());
        } catch (IOException e) {
            throw new RuntimeException(e);
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        }

序列化和反序列化结果

序列化:SerializeTest{name='myName', bornYear=2000, teacher=myTeacher, age=18, friends=[friendOne, friendTwo]}
序列化后:SerializeTest{name='myName', bornYear=2000, teacher=myTeacher2, age=0, friends=null}

transient 的中文字义为"临时的"(论英语的重要性),它可以阻止字段被序列化到文件中,在被反序列化后,transient 字段的值被设为初始值,比如 int 型的初始值为 0,对象型的初始值为 null

这是最简单的一种方式,因为这种方式让序列化机制看起来很方便(然后,我们在进行对象序列化时,只需要使用ObjectOutputStream和ObjectInputStream的writeObject(object)方法和readObject()方法,就可以把传入的对象参数序列化和反序列化了,其他不用管)。有时候想自己来控制序列化哪些成员,还有如何保存static和transient成员?

再注意:

该方式下,反序列化时不会调用该对象的构造器,但是会调用父类的构造器,如果父类没有默认构造器则会报错。static字段是类共享的字段,当该类的一个对象被序列化后,这个static变量可能会被另一个对象改变,所以这就决定了静态变量是不能序列化的,但如果再加上final修饰,就可以被序列化了,因为这是一个常量,不会改变。

2、实现Externalizable接口

Externalizable接口是继承自Serializable接口的,我们在实现Externalizable接口时,必须实现writeExternal(ObjectOutput)和readExternal(ObjectInput)方法,在这两个方法下我们可以手动的进行序列化和反序列化那些需要保存的成员变量。

public class SerializeExternalTest implements Externalizable {
    private static final long serialVersionUID = 213424233;
    private String name;
    private static String teacher;
    private final int bornYear;
    private static final String mother="mother";
    private transient int age;
    private transient List<String> friends;
    SerializeExternalTest(){
        bornYear  = 2000;
    }
    @Override
    public void writeExternal(ObjectOutput out) throws IOException {
        out.writeUTF(name);
        out.writeUTF(teacher);
        out.writeInt(bornYear);
        out.writeUTF(mother);
        out.writeInt(age);
        out.writeObject(friends);
    }

    @Override
    public void readExternal(ObjectInput in) throws ClassNotFoundException, IOException {
        this.name = in.readUTF();
        this.teacher = in.readUTF();
        //this.bornYear = in.readInt();final的不能改变值
        //this.mother = in.readUTF();final的不能改变值
        in.readInt();
        in.readUTF();
        this.age= in.readInt();
        this.friends = (List<String>) in.readObject();
    }
    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getTeacher() {
        return teacher;
    }

    public void setTeacher(String teacher) {
        SerializeExternalTest.teacher = teacher;
    }

    public int getBornYear() {
        return bornYear;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public List<String> getFriends() {
        return friends;
    }

    public void setFriends(List<String> friends) {
        this.friends = friends;
    }
    
}

Externalizable

public void externalizableT(){
        SerializeExternalTest test = new SerializeExternalTest();
        test.setName("myName");
        test.setAge(18);
        List<String> friends = new ArrayList<String>();
        friends.add("friendOne");
        friends.add("friendTwo");
        test.setFriends(friends);
        test.setTeacher("myTeacher");

        try {
            //序列化到文件
            ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(new File("D:\\user\\serialTest")));
            oos.writeObject(test);
            //从文件反序列化
            ObjectInputStream ois = new ObjectInputStream(new FileInputStream(new File("D:\\user\\serialTest")));
            SerializeTest objRead = (SerializeTest) ois.readObject();
        } catch (IOException e) {
            throw new RuntimeException(e);
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        }
    }

Externalizable结果

反序列化时,首先会调用对象的默认构造器(没有则报错,如果默认构造器不是public的也会报错),然后再调用readExternal方法。

这种方式一定要显式的序列化成员变量,使得整个序列化过程是可控制的,可以自己选择将哪些部分序列化。

3、实现Serializable接口并添加writeObject和readObject方法

实现Serializable接口,在该实现类中再增加writeObject方法和readObject方法。该方式要严格遵循以下两个方法的方法签名:

writeObject和readObject

在这两个方法里面需要使用stream.defaultWriteObject()序列化那些非static和非transient修饰的成员变量,static的和transient的变量则用**stream.writeObject(object)**显式序列化。

在序列化输出的时候,writeObject(object)会检查object参数,如果object拥有自己的writeObject()方法,那么就会使用它自己的writeObject()方法进行序列化。readObject()也采用了类似的步骤进行处理。如果object参数没有writeObject()方法,在readObject方法中就不能调用stream.readObject(),否则会报错。

public class SeriCtrolTest implements Serializable {
   private String a;
   private String b;
   private transient String c;
    SeriCtrolTest(String a,String b ,String c){
        this.a = "非瞬时默认实现"+a;
        this.b = "非顺时非默认实现"+b;
        this.c = "瞬时实现:"+c;
    }
    private void writeObject(ObjectOutputStream stream) throws IOException {
        stream.defaultWriteObject();
        stream.writeObject(b);
        stream.writeObject(c);
    }
    private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException {
        stream.defaultReadObject();
        stream.readObject();
        b="null";
        c = stream.readObject().toString();
    }

    @Override
    public String toString() {
        return "SeriCtrolTest{" +
                "a='" + a + '\'' +
                ", b='" + b + '\'' +
                ", c='" + c + '\'' +
                '}';
    }
    public static void main(String[] args){
        SeriCtrolTest sCtrl = new SeriCtrolTest("test1","test2","test3");
        System.out.println("序列化之前");
        System.out.println(sCtrl);

        ByteArrayOutputStream out = new ByteArrayOutputStream();
        try {
            ObjectOutputStream oos = new ObjectOutputStream(out);
            oos.writeObject(sCtrl);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        System.out.println("反序列化操作之后");
        ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray());

    }
}

参考文献:

浅谈一下Java的Serializable 接口(序列化与反序列化)-CSDN博客

Java序列化进阶:Java内置序列化的三种方式-腾讯云开发者社区-腾讯云

相关推荐
XiaoLeisj2 小时前
【JavaEE初阶 — 多线程】单例模式 & 指令重排序问题
java·开发语言·java-ee
paopaokaka_luck2 小时前
【360】基于springboot的志愿服务管理系统
java·spring boot·后端·spring·毕业设计
dayouziei2 小时前
java的类加载机制的学习
java·学习
Yaml44 小时前
Spring Boot 与 Vue 共筑二手书籍交易卓越平台
java·spring boot·后端·mysql·spring·vue·二手书籍
小小小妮子~4 小时前
Spring Boot详解:从入门到精通
java·spring boot·后端
hong1616884 小时前
Spring Boot中实现多数据源连接和切换的方案
java·spring boot·后端
aloha_7894 小时前
从零记录搭建一个干净的mybatis环境
java·笔记·spring·spring cloud·maven·mybatis·springboot
记录成长java5 小时前
ServletContext,Cookie,HttpSession的使用
java·开发语言·servlet
睡觉谁叫~~~5 小时前
一文解秘Rust如何与Java互操作
java·开发语言·后端·rust
程序媛小果5 小时前
基于java+SpringBoot+Vue的旅游管理系统设计与实现
java·vue.js·spring boot