Serializable

这是最简单的实现序列化的方式,在Java中,只要一个类实现了java.io.Serializable接口,那么它就可以被序列化。
例如以下代码:Person实现了Serializable接口。我们就可以对这个类的实例进行序列化。

public class Person implements Serializable {

    private String name = null;

    private Integer age = null;

    private Gender gender = null;

    public Person() {
        System.out.println("none-arg constructor");
    }

    public Person(String name, Integer age, Gender gender) {
        System.out.println("arg constructor");
        this.name = name;
        this.age = age;
        this.gender = gender;
    }

    public String getName() {
        return name;
    }

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

    public Integer getAge() {
        return age;
    }

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

    public Gender getGender() {
        return gender;
    }

    public void setGender(Gender gender) {
        this.gender = gender;
    }

    @Override
    public String toString() {
        return "[" + name + ", " + age + ", " + gender + "]";
    }
}

简单写一个序列化程序:

public class SimpleSerial {

    public static void main(String[] args) throws Exception {
        File file = new File("person.out");

        ObjectOutputStream oout = new ObjectOutputStream(new FileOutputStream(file));
        Person person = new Person("John", 101, Gender.MALE);
        oout.writeObject(person);
        oout.close();

        ObjectInputStream oin = new ObjectInputStream(new FileInputStream(file));
        Object newPerson = oin.readObject(); // 没有强制转换到Person类型
        oin.close();
        System.out.println(newPerson);
    }
}

注意一点,就是反序列化的时候,并没有调用任何Person类构造函数,因为是直接从字节流中还原出来的。当然在反序列化的时候,要保证反序列化的相关类在Classpath中能够找到,否则回报ClassNotFoundException。在序列化对象时,不仅会序列化当前对象本身,还会对该对象引用的其它对象也进行序列化,同样地,这些其它对象引用的另外对象也将被序列化,以此类推。所以,如果一个对象包含的成员变量是容器类对象,而这些容器所含有的元素也是容器类对象,那么这个序列化的过程就会较复杂,开销也较大。

transient

当某个字段被声明为transient后,默认序列化机制就会忽略该字段。通过这种方式可以将一些字段在序列化的过程中过滤掉。

writeObject()方法与readObject()方法

writeObject()方法与readObject()方法可以实现在默认序列化机制的基础上自定义序列化过程。要注意的一点就是Write的顺序和read的顺序需要对应。

Externalizable

JDK中提供了另一个序列化接口,Externalizable继承了Serializable接口。使用该接口之后,之前基于Serializable接口的序列化机制就将失效。上述提到的transient、writeObject()方法与readObject()方法都会失效。当使用该接口时,序列化的细节需要由程序员去完成。有两个方法writeExternal()与readExternal()来实现序列化和反序列化。 另外,若使用Externalizable进行序列化,当读取对象时,会调用被序列化类的无参构造器去创建一个新的对象,然后再将被保存对象的字段的值分别填充到新对象中。由于这个原因,实现Externalizable接口的类必须要提供一个无参的构造器,且它的访问权限为public。

readResolve()方法

当我们使用Singleton模式时,应该是期望某个类的实例应该是唯一的,但如果该类是可序列化的,那么情况可能会略有不同。默认情况下反序列化之后会破坏单例模式。无论是实现Serializable接口,或是Externalizable接口,当从I/O流中读取对象时,readResolve()方法都会被调用到。实际上用readResolve()中返回的对象直接替换在反序列化过程中创建的对象,而被创建的对象则会被垃圾回收掉。这样就实现了在序列化的场景下保证单例模式正常运行。

参考

具体的序列化逻辑可以参考 ObjectOutputStreamObjectInputStream的代码逻辑。