Serialization and deserialization of Java objects

Keywords: Java Attribute

Previous articles described the use of byte stream character streams when we used the DataOutputStream stream to output each attribute value in an object to the stream one by one, instead of reading it out.This behavior is cumbersome to us, especially when there are many attribute values in this object.Based on this, the serialization mechanism of objects in Java can solve this kind of operation very well.This article provides a brief introduction to Java object serialization, with the following main points:

  • Simple code implementation
  • Basic algorithm for serialization implementation
  • Two special cases
  • Custom serialization mechanism
  • Serialized version control

1. Simple code implementation
Before introducing how to use object serialization, look at how we previously stored data of an object type.

//Simply define a Student class
public class Student {

    private String name;
    private int age;

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

    public void setName(String name){
        this.name = name;
    }
    public void setAge(int age){
        this.age = age;
    }
    public String getName(){
        return this.name;
    }
    public int getAge(){
        return this.age;
    }
    //Rewrite toString
    @Override
    public String toString(){
        return ("my name is:"+this.name+" age is:"+this.age);
    }
}
//The main method implements writing objects to a file and reading them out
public static void main(String[] args) throws IOException{

        DataOutputStream dot = new DataOutputStream(new FileOutputStream("hello.txt"));
        Student stuW = new Student("walker",21);
        //Write this object to a file
        dot.writeUTF(stuW.getName());
        dot.writeInt(stuW.getAge());
        dot.close();

        //Read Objects from File
        DataInputStream din = new DataInputStream(new FileInputStream("hello.txt"));
        Student stuR = new Student();
        stuR.setName(din.readUTF());
        stuR.setAge(din.readInt());
        din.close();

        System.out.println(stuR);
    }
Output result: my name is:walker age is:21

This code writing is obviously cumbersome, so let's see how serialization can be used to complete saving object information.

public static void main(String[] args) throws IOException, ClassNotFoundException {

        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("hello.txt"));
        Student stuW = new Student("walker",21);
        oos.writeObject(stuW);
        oos.close();

        //Read the object from the file and return
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("hello.txt"));
        Student stuR = (Student)ois.readObject();
        System.out.println(stuR);
    }

When writing a file, only one statement is used, that is, writeObject, and when reading, only one statement is used, readObject.And those set, get methods in Student are not used.Is it simple?The details of the implementation follow.

2. Basic Algorithms for Serialization
In this mechanism, each object corresponds to a unique sequence number, and each object is saved according to this sequence number for each different object. Object serialization refers to saving and reading using the sequence number of each object.For each object, the basic information of the object is saved in the stream when it is first encountered. If the object that is currently encountered has been saved, the information will not be saved again, but the serial number of the object will be recorded instead (because there is no need for data to be saved repeatedly).For the read case, each object encountered from the stream, if encountered for the first time, outputs directly, if read the sequence number of an object, the associated object will be found, the output.
To be serializable, an object must implement the interface java.io.Serializable; this is a markup interface and does not implement any methods.Our ObjectOutputStream stream is a stream that converts object information into bytes, and the constructor is as follows:

public ObjectOutputStream(OutputStream out)

That is, all byte streams can be passed in as parameters, compatible with all byte operations.In this stream, writeObject and readObject methods are defined, and both serialized and deserialized objects are implemented.Of course, we can also customize the serialization mechanism by implementing these two methods in classes, as described later.Here we only need to understand the entire serialization mechanism. All object data will be saved in one copy, and only the corresponding serial number will be saved for the same object to reappear.Next, his basic algorithm is visually perceived through two special cases.

3. Two Special Examples
Start with the first example:

public class Student implements Serializable {

    String name;
    int age;
    Teacher t;  //Another object type

    public Student(){}
    public Student(String name,int age,Teacher t){
        this.name = name;
        this.age=age;
        this.t = t;
    }

    public void setName(String name){this.name = name;}
    public void setAge(int age){this.age = age;}
    public void setT(Teacher t){this.t = t;}
    public String getName(){return this.name;}
    public int getAge(){return this.age;}
    public Teacher getT(){return this.t;}
}

public class Teacher implements Serializable {
    String name;

    public Teacher(String name){
        this.name = name;
    }
}

public static void main(String[] args) throws IOException, ClassNotFoundException {

        Teacher t = new Teacher("li");
        Student stu1 = new Student("walker",21,t);
        Student stu2 = new Student("yam",22,t);
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("hello.txt"));
        oos.writeObject(stu1);
        oos.writeObject(stu2);

        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("hello.txt"));
        Student stuR1 = (Student)ois.readObject();
        Student stuR2 = (Student)ois.readObject();

        if (stuR1.getT() == stuR2.getT())
            System.out.println("Same object");
    }

The result is obvious, outputting the same object.We define two Student-Type objects in the main function, but they both refer to the same teacher object internally.After the serialization, the two objects are deserialized, and by comparing whether the teacher object inside them is the same instance, it can be seen that t was written to the stream when serializing the first student object, but when encountering the teacher object instance of the second student object, it is found that the previous one has been writtenInstead of writing to the stream, save the corresponding serial number as a reference.Of course, when deserializing, it works similarly.This is the same as the basic algorithm we described above.
See the second special example below:

public class Student implements Serializable {

    String name;
    Teacher t;

}

public class Teacher implements Serializable {
    String name;
    Student stu;

}

public static void main(String[] args) throws IOException, ClassNotFoundException {

        Teacher t = new Teacher();
        Student s =new Student();
        t.name = "walker";
        t.stu = s;
        s.name = "yam";
        s.t = t;

        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("hello.txt"));
        oos.writeObject(t);
        oos.writeObject(s);
        oos.close();

        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("hello.txt"));
        Teacher tR = (Teacher)ois.readObject();
        Student sR = (Student)ois.readObject();
        if(tR == sR.t && sR == tR.stu)System.out.println("ok");

    }

The result of the output is ok, which can be called a circular reference.From the results, we can see that the mutual reference relationship between the two objects before serialization still exists after serialization.In fact, according to the judgment algorithm we introduced earlier, we first serialized the teacher object, because it refers to the student object internally, both of which are encountered for the first time, so we serialized them into the stream, then we de-serialized the student object and found that both the object and the internal teacher object wereAfter being serialized, only the corresponding serial number is saved.Recover the object based on its serial number when reading.

4. Custom Serialization Mechanism
In summary, we have finished the basic knowledge of serialization and deserialization.However, we often have some special requirements. Although the default serialization mechanism has been perfected, sometimes it still can not meet our needs.So let's see how to customize the serialization mechanism.In a custom serialization mechanism, we use a keyword, which is also what we used to encounter when we looked at the source code, transient.Declaring a field transient is equivalent to telling the default serialization mechanism that you don't want to write it to the stream for me, but I'll handle it myself.,

public class Student implements Serializable {

    String name;
    transient int age;

    public String toString(){
        return this.name + ":" + this.age;
    }
}

public static void main(String[] args) throws IOException, ClassNotFoundException {

        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("hello.txt"));
        Student stu = new Student();
        stu.name = "walker";stu.age = 21;
        oos.writeObject(stu);

        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("hello.txt"));
        Student stuR = (Student)ois.readObject();

        System.out.println(stuR);
    }
Output result: walker:0

Didn't we assign an initial value to the age field? How could it be zero?As we mentioned above, fields modified by transient s are not written to the stream, and naturally read out has no value, defaulting to 0.Let's see how we can serialize this age ourselves.

//The modified student class, main method has not changed, you can look up
public class Student implements Serializable {

    String name;
    transient int age;

    private void writeObject(ObjectOutputStream oos) throws IOException {
        oos.defaultWriteObject();

        oos.writeInt(25);
    }

    private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
        ois.defaultReadObject();

        age = ois.readInt();
    }

    public String toString(){
        return this.name + ":" + this.age;
    }
}
Output result: walker:25

The result is neither 21 nor 0 that we initialized, but 25 that we wrote in the writeObject method.Now let's take a closer look at what each step means.First, to achieve custom serialization, you need to implement two methods in the class defined by the object, writeObject and readObject, and the format must be the same as what is pasted above. The author tried to change the method modifier, which resulted in unsuccessful serialization.This is because Java uses a reflection mechanism to check whether the two methods are implemented in the class in which the object resides. If not, serialize all fields using this method in the default ObjectOutputStream, and if so, execute the method you implemented yourself.
Next, look at the details of the implementation of these two methods. First, look at the writeObject method. The parameter is of type ObjectOutputStream. This gets the ObjectOutputStream object that we defined in the main method. Otherwise, how does it know where to write the object?The first line we call oos.defaultWriteObject(); this method implements the function of writing any transient ly modified fields in the current object to the stream, and the second statement explicitly calls the writeInt method to write the age value to the stream.Reading is similar, and will not be repeated here.

V. Version Control
Finally, let's look at version control issues in the serialization process.After we serialize an object into the stream, the structure of the class corresponding to that object has changed. What happens if we read the previously saved object from the stream again?In this case, if a field in the original class is deleted, the corresponding field output from the stream will be ignored.If a field is added to the original class, the value of the new field is the default value.If the field type changes, throw an exception.Each class in Java has a variable that records the version number: static final serivalVersionUID = 1156165165L, where the value is used only for demonstration purposes and does not correspond to any class.This version number is calculated from some attribute information such as fields in this class, which is more unique.Each time you read it, you will compare the previous and current version numbers to confirm whether there is a version inconsistency. If the versions are inconsistent, they will be handled separately as described above.

The serialization of the object is finished. If there is something wrong with it, I hope you can point it out!

Posted by LTJason on Sun, 14 Jul 2019 09:18:33 -0700