1. The Serializable interface

Serialization and deserialization are reciprocal methods in Java, where serialization can write a Java object to a file system for storage or send it over the network to other applications. Deserialization groups, on the other hand, can restore (reconstruct) serialized data into Java objects. Serialization capability can be implemented by declaring the implementation of the Java.io.Serializable interface. There are two criteria for determining whether a class has serialization capability:

  • Whether this class declares an implementationjava.io.Serializableinterface
  • Whether the superclass of this class declares an implementationjava.io.Serializableinterface

Serialization and deserialization examples and custom serialization and deserialization examples are already available on the web and will not be covered here. Now consider a slightly special case: what happens when a subclass declares that it implements the java.io.Serializable interface and inherits a superclass that does not declare the java.io.Serializable interface? ex:

class A{
    int field1;
    //getter and setter
}
class B extends A implements java.io.Serializable{
    //...
}
Copy the code

2. Examples and running results

Define private domain and protected domain respectively in the parent class of 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 StringtoString() {
        return "UserParent{" +
                "firstName='" + firstName + '\'' + ", lastName='" + lastName + '\'' + ", accountNumber=" + accountNumber + ", dateOpened=" + dateOpened + '}'; }}Copy the code

The User class implements the java.io.Serializable interface and inherits UserParent, defines a private domain, and a custom deserialization function to serialize writeObject, readObject:

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 + '\''+'}'; }}Copy the code

The last call method is:

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);

    }
Copy the code

The final running result is:

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'}
Copy the code

3. Perform process analysis by example

The first example creates a User object that calls UserParent’s constructor before calling User’s constructor to initialize its value, so the console prints UserParent default constructor called! Statements.

The next example serializes the testUser object and stores its serialized bits in a “user.ser” file, printing testUser serialIZABLE Completed! Statements. Note: When serializing a Java object, the object’s type — the metadata of the class, the value of the domain, and the type — is converted into a series of corresponding bits that are written to a file or transmitted over the network. The deserialization method then uses this information to reconstruct the object.

Finally, the instance deserializes and reconstructs the object from the file, printing the rest of the output statement. But why is the no-parameter constructor in UserParent called instead of User?

4. Deserialization

Take a look at the official documentation for the Serializable interface. It says:

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.

Two conditions must be met for a declared java.io.Serializable interface to inherit from a superclass that does not declare the java.io.Serializable interface:

  • Implement the corresponding method to save the state of the restored object in the subclass
  • No declared implementationjava.io.SerializableThe superclass of the interface must have an accessible no-argument constructor

As with instantiating a subclass Object, entering a subclass constructor without an explicit declaration automatically inserts the parent class’s parameterless constructor in the first line all the way to the source, the Object class’s parameterless constructor, (See section 3.4.4.Constructor Chaining and the Default Constructor for the reason.) Deserialization corresponds to the above, which requires that all superclasses declare the Serializable interface, where a no-parameter constructor is required if any superclass does not have an interface declaration. Therefore, during deserialization, the JVM traverses from the topmost superclass of the subclass to itself. If all superclasses declare the Serializable interface, the JVM eventually reaches the object class itself and creates instances. If you encounter a superclass with no interface declaration, call its default constructor to create an instance in memory.

So now that the JVM has an instance in memory using the UserParent no-argument constructor, no class constructors are called after that. After that, the JVM reads the data in the file to set the type and domain information belonging to testUser. After the instance is created, the JVM first sets its static fields, and then internally calls the default readObject() method (if there is no override, the override method is called instead), which is responsible for restoring the object state. Once the readObject() method completes, the deserialization process is complete.

For an explanation of why User’s no-argument constructor is not executed in deserialization, see the official documentation for the readObject method, point 11. What does a constructor do first? A: Constructors initialize object variables using default values or values assigned internally by constructors. The deserialized data already contains the required values, and the readObject method is called to restore the values.

5. To summarize

1. During deserialization, the JVM traverses from the topmost superclass of the subclass to itself. If all superclasses declare the Serializable interface, the JVM eventually reaches the object class itself and creates instances. If you encounter a superclass with no interface declaration, call its default constructor to create an instance in memory

6. Other notable issues

1. The private domain set in the UserParent class (firstName; lastName; AccountNumber;) Is not inherited by User. See here:

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. Mike. Ua/orelly/Java…

【 3 】 howtodoinjava.com/java/serial…

[4] docs.oracle.com/javase/7/do…

[5] docs.oracle.com/javase/spec…