JDK 1.7 java.io 源码学习之Serializable接口

时间:2021-11-23 17:37:46

Java 编程思想中是这么描述序列化的:
Java的对象序列化将那些实现了Serializable接口的对象转换成一个字节序列,并能够在以后将这个字节序列完全恢复为原来的对象。这一过程甚至可通过网络进行,自动弥补不同操作系统之间的差异

对象序列化主要是为了支持如下两种主要特性:
一是Java的远程方法调用RMI,使存活于其他计算机上的对象使用起来就像是存活于本机一样。再者,对Java Beans来说,对象的序列化也是必需的,使用一个Bean时,在设计阶段对它的状态信息进行配置,并保存这些信息,在程序启动时进行后期恢复。

对象序列化为字节的过程:
创建某些OutputStream对象,然后将其封装在一个ObjectOutputStream对象内,调用writeObject()即可将对象序列化,并将其发送给OutputStream

字节序列还原为对象的过程:
创建某些InputStream对象,然后将其封装在一个ObjectInputStream对象内,调用readObject()即可还原为对象,还原回来的对象是Object,需要类型转换

对象序列化时基于字节的,要使用字节输入流/输出流

对象序列化不仅保存了对象的“全景图”,而且能追踪对象内所包含的所有引用

下面通过一些示例演示序列化相关的一些细节问题

反序列化回来的对象和原对象内容一致,但是一个新对象

class SerializableDemo implements Serializable {
    /** * */
    private static final long serialVersionUID = -707015875588596115L;

    private String name;

    public SerializableDemo(String name) {
        super();
        this.name = name;
    }

    public String getName() {
        return name;
    }

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

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + ((name == null) ? 0 : name.hashCode());
        return result;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        SerializableDemo other = (SerializableDemo) obj;
        if (name == null) {
            if (other.name != null)
                return false;
        } else if (!name.equals(other.name))
            return false;
        return true;
    }

    @Override
    public String toString() {
        return "SerializableDemo [name=" + name + "]";
    }
}
public static void seri(SerializableDemo demo) {
    try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("my.txt"))) {
        oos.writeObject(demo);
        oos.flush();
    } catch (Exception e) {
        e.printStackTrace();
    }
}

public static SerializableDemo desri() {
    try (ObjectInputStream oin = new ObjectInputStream(new FileInputStream("my.txt"))) {
        SerializableDemo demo = (SerializableDemo) oin.readObject();
        return demo;
    } catch (Exception e) {
        e.printStackTrace();
        return null;
    }
}
SerializableDemo demo = new SerializableDemo("Tom");
seri(demo);
SerializableDemo newDemo = desri();
System.out.println(newDemo);
System.out.println(demo == newDemo); // false
System.out.println(demo.equals(newDemo)); // true

serialVersionUID的作用
字面意思是序列化版本号,Eclipse中默认有两种生成方式:default serial version ID 和 generated serial version ID,前者是1L,后者是根据类名,属性等生成的随机Long类型的数
如果修改一下类,重新生成serialVersionUID,从原序列化文件中反序列化会失败

class SerializableDemo implements Serializable {
    /** * */
    private static final long serialVersionUID = -9071009327174509480L;

    /** * */
// private static final long serialVersionUID = -707015875588596115L;

    private String name;

    private int age;

    public SerializableDemo(String name, int age) {
        super();
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

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

    public int getAge() {
        return age;
    }

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

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + age;
        result = prime * result + ((name == null) ? 0 : name.hashCode());
        return result;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        SerializableDemo other = (SerializableDemo) obj;
        if (age != other.age)
            return false;
        if (name == null) {
            if (other.name != null)
                return false;
        } else if (!name.equals(other.name))
            return false;
        return true;
    }

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

}
SerializableDemo newDemo = desri();
System.out.println(newDemo);

但是如果还是使用原来的serialVersionUID则可以反序列化成功,只是age属性的值是0

若希望类的不同版本对序列化兼容,需要确保类的不同版本具有相同的serialVersionUID;
若不希望类的不同版本对序列化兼容,需要确保类的不同版本具有不同的serialVersionUID;

对象中存在引用对象时的序列化,其引用的对象也需要实现Serializable接口

class Address implements Serializable {
    /** * */
    private static final long serialVersionUID = -5432410643914580007L;

    private String city;

    public Address(String city) {
        super();
        this.city = city;
    }

    public String getCity() {
        return city;
    }

    public void setCity(String city) {
        this.city = city;
    }

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + ((city == null) ? 0 : city.hashCode());
        return result;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        Address other = (Address) obj;
        if (city == null) {
            if (other.city != null)
                return false;
        } else if (!city.equals(other.city))
            return false;
        return true;
    }

    @Override
    public String toString() {
        return "Address [city=" + city + "]";
    }

}

class SerializableDemo implements Serializable {
    /** * */
    private static final long serialVersionUID = 950815272813597778L;

    private String name;

    private int age;

    private Address address;

    public SerializableDemo(String name, int age) {
        super();
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

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

    public int getAge() {
        return age;
    }

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

    public Address getAddress() {
        return address;
    }

    public void setAddress(Address address) {
        this.address = address;
    }

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + age;
        result = prime * result + ((name == null) ? 0 : name.hashCode());
        return result;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        SerializableDemo other = (SerializableDemo) obj;
        if (age != other.age)
            return false;
        if (name == null) {
            if (other.name != null)
                return false;
        } else if (!name.equals(other.name))
            return false;
        return true;
    }

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

}
SerializableDemo demo = new SerializableDemo("Tom", 20);
seri(demo);

若Address没有实现Serializable接口,则SerializableDemo对象序列化会失败

当父类实现了Serializable接口时,其子类不需要显示声明Serializable接口也可以序列化
当父类未实现了Serializable接口,但子类实现了Serizlizable接口,子类序列化时会失败

static 关键字修饰变量,不受序列化反序列化影响,序列化和反序列化都是基于对象,但是static是类的任何实例对象共享的

transient 关键字 修饰的变量,也不受序列化反序列化影响,该变量的值反序列化时是该变量数据类型的默认值,相当于隐藏了原来的值

class SerializableDemo implements Serializable {

    /** * */
    private static final long serialVersionUID = 164659918544315637L;

    private String name;

    private transient int age;

    public SerializableDemo(String name, int age) {
        super();
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

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

    public int getAge() {
        return age;
    }

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

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

}
SerializableDemo demo = new SerializableDemo("Tom", 30);
seri(demo);
SerializableDemo newDemo = desri();
System.out.println(newDemo);//SerializableDemo [name=Tom, age=0]

通过writeObject()方法和readObject()方法自定义序列化策略

class SerializableDemo implements Serializable {

    /** * */
    private static final long serialVersionUID = 164659918544315637L;

    private String name;

    private transient int age;

    public SerializableDemo(String name, int age) {
        super();
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

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

    public int getAge() {
        return age;
    }

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

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

    private void writeObject(ObjectOutputStream oos) throws IOException {
        oos.defaultWriteObject();
        age += 5;
        oos.writeInt(age);
    }

    private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
        ois.defaultReadObject();
        age = ois.readInt();
        age -= 5;
    }

}

验证序列化和反序列化,age的值会被正常还原,但是实际是在writeObject和readObject方法中做过处理的,如果别人拿到了字节序列,但是不知道还原逻辑,也是没有意义的

SerializableDemo demo = new SerializableDemo("Tom", 30);
seri(demo);
SerializableDemo newDemo = desri();
System.out.println(newDemo);

通过writeObject()方法和readObject()方法自定义序列化策略是基于ObjectInputStream和ObjectOutputStream里的readObject和writeObject方法实现的,其内部有一个方法调用栈:
writeObject –> writeObject0 –> writeOrdinaryObject –> writeSerialData –> invokeWriteObject
readObject –> readObject0 –> readOrdinaryObject – readSerialData –>invokeReadObject

readObject和writeObject中属性的顺序必须一致的,反之会反序列化失败或错误
自定义序列化策略不受transient关键字影响
具体详情留待ObjectInputStream和ObjectOutputStream源码学习

还有一种实现自定义序列化策略的即实现Externalizable接口,该接口继承了Serializable接口,扩展定义了两个方法:

public interface Externalizable extends java.io.Serializable {
    void writeExternal(ObjectOutput out) throws IOException;
    void readExternal(ObjectInput in) throws IOException, ClassNotFoundException;
}
class SerializableDemo implements Externalizable {

    /** * */
    private static final long serialVersionUID = 164659918544315637L;

    private String name;

    private transient int age;

    public SerializableDemo() {
        super();
    }

    public SerializableDemo(String name, int age) {
        super();
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

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

    public int getAge() {
        return age;
    }

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

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

    @Override
    public void writeExternal(ObjectOutput out) throws IOException {
        out.writeObject(name);
        out.writeInt(age);
    }

    @Override
    public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
        name = (String) in.readObject();
        age = in.readInt();
    }

}
SerializableDemo demo = new SerializableDemo("Tom", 30);
seri(demo);
SerializableDemo newDemo = desri();
System.out.println(newDemo);

类似于readObject和writeObject的模式,实现readExternal和writeExternal即可
这里有两个需要注意:
1. 必须有无参构造器,不然会反序列化失败
2. writeExternal和readExternal中属性的顺序必须一致,不然也会反序列化失败
同样的也不受transient关键字影响

暂时就这么多,小结一下:
1. 序列化即将对象转化为字节序列,反序列化即将字节序列还原为对象
2. 默认的序列化机制实现Serializable接口即可
3. 序列化和反序列化基于字节流ObjectInputStream和ObjectOutputStream操作
4. 序列化机制序列化的是对象的“全景图”,包含引用对象,继承等关系
5. 序列化对象的引用对象也必须实现序列化接口
6. 子类实现了序列化接口,父类也必须实现序列化接口
7. 父类实现了序列化接口,子类默认实现序列化接口
8. transient 和 static 关键字修饰的变量不受默认序列化机制的影响
9. 可通过readObject和writeObject对象实现自定义序列化策略,基于ObjectInputStream和ObjectOutputStrema的readObject和writeObject原理,且不受transient关键字影响,但方法内属性顺序必须一致
10. 可通过实现Externalizable接口实现自定义序列化策略,该方式必须有无参构造器且,方法内属性顺序必须一致,也不受transient关键字影响