Java中Deserialization反序列化处理过程

1.Serializable接口

Java中序列化与反序列化是互逆的方法,其中序列化可以把一个Java对象写入到文件系统中存储或是将其通过网络来发送至其他应用程序。反序列化组正好相反,可以把已经序列化的数据恢复(重构)成为Java对象。序列化能力可以通过声明实现 java.io.Serializable 接口来实现。可以根据两个条件来判断一个类是否具有序列化能力:

  • 这个类是否声明实现 java.io.Serializable 接口
  • 这个类的超类是否声明实现 java.io.Serializable 接口

序列化以及反序列化例子以及自定义序列化以及反序列化例子在网上已经有了许多,这里不再赘述。现在考虑一种稍微特殊的情况:当子类声明实现java.io.Serializable 接口并继承没有声明实现java.io.Serializable 接口的超类的时候,情况又是如何呢?ex:

class A{
    int field1;
    //getter and setter
}
class B extends A implements java.io.Serializable{
    //...
}
复制代码

2.示例以及运行结果

在User的父类中分别定义私有域和保护域:

public class UserParent {
    private String firstName;
    private String lastName;
    private int accountNumber;
    protected Date dateOpened;
    public UserParent(){
        System.out.println("UserParent default constructor called!");
    }
    //...
    //getter and setter
    @Override
    public String toString() {
        return "UserParent{" +
                "firstName='" + firstName + '\'' +
                ", lastName='" + lastName + '\'' +
                ", accountNumber=" + accountNumber +
                ", dateOpened=" + dateOpened +
                '}';
    }
}
复制代码

在User类中声明实现java.io.Serializable 接口并继承UserParent,定义私有域,以及自定义的用来序列化反序列化函数writeObjectreadObject:

public class User extends UserParent implements Serializable {
    private static final long serialVersionUID = 7829136421241571165L;
    private String userVar;
    public User(){
        System.out.println("User default constructor called!");
    }

    public User(String firstName, String lastName, int accountNumber, Date dateOpened) {
        super.setFirstName(firstName);
        super.setLastName(lastName);
        super.setAccountNumber(accountNumber);
        super.setDateOpened(dateOpened);
    }
    private void writeObject(ObjectOutputStream objectOutputStream) throws IOException {
        objectOutputStream.writeUTF(super.getFirstName());
        objectOutputStream.writeUTF(super.getLastName());
        objectOutputStream.writeInt(super.getAccountNumber());
        objectOutputStream.writeLong(dateOpened.getTime());
        objectOutputStream.writeUTF(userVar);
    }
    private void readObject(ObjectInputStream objectInputStream) throws IOException {
        super.setFirstName(objectInputStream.readUTF());
        super.setLastName(objectInputStream.readUTF());
        super.setAccountNumber(objectInputStream.readInt());
        dateOpened = new Date(objectInputStream.readLong());
        userVar = objectInputStream.readUTF();
    }
    public void setUserVar(String userVar) {
        this.userVar = userVar;
    }

    @Override
    public String toString() {
        return "User{" + super.toString()+
                "userVar='" + userVar + '\'' +
                '}';
    }
}
复制代码

最后调用方法为:

public static void main(String[] args) throws IOException, ClassNotFoundException {
        String testData = "testData";
        int testNum = 1;
        Date testDate = new Date();
        User testUser = new User(testData,testData,testNum,testDate);
        testUser.setUserVar(testData);

        String name = "User.ser";
        FileOutputStream fileOut = new FileOutputStream(name);
        ObjectOutputStream objectOutputStream
                = new ObjectOutputStream(fileOut);
        objectOutputStream.writeObject(testUser);
        fileOut.close();
        System.out.println("testUser serializable completed! ");

        FileInputStream fileIn = new FileInputStream(name);
        ObjectInputStream objectInputStream
                = new ObjectInputStream(fileIn);
        User deserializableUser = (User)objectInputStream.readObject();
        fileIn.close();
        System.out.println(deserializableUser);

    }
复制代码

最后运行结果为:

UserParent default constructor called!
testUser serializable completed! 
UserParent default constructor called!
User{UserParent{firstName='testData', lastName='testData', accountNumber=1, dateOpened=Sat Dec 28 15:50:00 CST 2019}userVar='testData'}
复制代码

3.实例执行流程分析

首先示例创建了一个User对象,在调用User的构造方法初始化值之前,User的构造方法先调用了UserParent的构造方法,因此控制台打印UserParent default constructor called!语句。

接着示例序列化testUser这个对象并将其序列化的比特数据存储在"User.ser"文件中,并打印testUser serializable completed!语句 。注:当序列化一个Java对象的时候,此对象的类型即类的元数据、域的值以及类型转换成为一系列对应的比特并写入文件或者经过网络传输,然后反序列化方法通过此信息重新构造对象。

最后实例从文件中反序列化重新构造对象,输出剩余输出语句。但是为什么没有调用User中的无参构造方法而调用了UserParent中的无参构造方法呢?

4.反序列化处理

首先看下官方文档对于Serializable接口说明。其中提到:

To allow subtypes of non-serializable classes to be serialized, the subtype may assume responsibility for saving and restoring the state of the supertype's public, protected, and (if accessible) package fields. The subtype may assume this responsibility only if the class it extends has an accessible no-arg constructor to initialize the class's state. It is an error to declare a class Serializable if this is not the case. The error will be detected at runtime.

可以得知,要使一个声明实现java.io.Serializable 接口并继承没有声明实现java.io.Serializable 接口的超类的子类的时候要满足两个条件:

  • 在子类中实现对应的保存恢复对象状态的方法
  • 没有声明实现java.io.Serializable 接口的超类要有可访问的无参构造函数

如同实例化一个子类对象一样,在进入子类的构造方法的时候若没有显式声明,会自动在首行插入父类的无参构造方法一直到根源即Object类中无参构造方法,(原因点这里见3.4.4.Constructor Chaining and the Default Constructor节)。反序列化与上述相对应,反序列化要求所有超类都声明serializable接口,其中若任何一个超类没有接口声明则必须有一个无参构造函数。因此反序列化过程中,JVM遍历自子类的最顶层的超类到它本身,若所有超类都声明serializable接口则JVM最终会到达对象类本身并创建实例;若遇见一个超类没有接口声明则调用其默认构造函数在内存中创建实例

因此现在JVM在内存中使用UserParent无参构造函数已经得到了一个实例,在此之后不会调用任何类的构造函数。在这之后JVM读取文件中的数据来设置属于testUser的类型信息、域信息。在创建实例之后,JVM首先设置它的静态字段,然后在内部调用默认的readObject()方法(如果没有覆盖,反之调用覆盖的方法),该方法负责恢复对象状态。readObject()方法完成后,反序列化过程就结束了。

关于为什么在反序列化中没有执行User的无参构造函数,看下官方文档对于readObject方法说明,第11点。首先不妨来看下构造函数的作用是什么?答:构造函数使用默认值或构造函数内部分配的值来初始化对象变量。而在反序列化数据中已经包含了所需要的值,并调用readObject方法来恢复值。

5.总结

1.反序列化过程中,JVM遍历自子类的最顶层的超类到它本身,若所有超类都声明serializable接口则JVM最终会到达对象类本身并创建实例;若遇见一个超类没有接口声明则调用其默认构造函数在内存中创建实例

6.其他一些值得注意的问题

1.UserParent类中设置的私有域(firstName;lastName;accountNumber;)并没有被User所继承。请见这里

Members of a class that are declared private are not inherited by subclasses of that class.

Only members of a class that are declared protected or public are inherited by subclasses declared in a package other than the one in which the class is declared.

Constructors, static initializers, and instance initializers are not members and therefore are not inherited.

Reference

【1】docs.oracle.com/javase/7/do…

【2】docstore.mik.ua/orelly/java…

【3】howtodoinjava.com/java/serial…

【4】docs.oracle.com/javase/7/do…

【5】docs.oracle.com/javase/spec…

猜你喜欢

转载自juejin.im/post/5e070a00e51d4557f71a93a2