Serialization / deserialization, I’ve endured you for a long time

Time:2021-9-18

Serialization / deserialization, I've endured you for a long time

The GitHub open source project: github.com/hansonwang99/javacollection has been included in this article. There are detailed self-study programming learning routes, interview questions and experience, programming materials and a series of technical articles. The resources are constantly updated


Tool man

Once upon a time, the understanding of Java serialization remained in “implementation”Serializbale“Interface” is not ready until

So this time I took the time to pick up the dusty Java programming thought again. Just like combing some knowledge of enumeration, I reviewed the knowledge points of “serialization and deserialization” again.


What is serialization for?

The original intention of serialization is to “transform” a Java object into a byte sequence, so as to facilitate persistent storage to disk and prevent the object from disappearing from memory after the program runs. In addition, transforming it into a byte sequence is also more convenient for network transportation and propagation, so it is conceptually well understood:

  • serialize: converts a Java object into a sequence of bytes.
  • Deserialization: restore the byte sequence to the original Java object.

Serialization / deserialization, I've endured you for a long time

In a sense, the serialization mechanism also makes up for some differences in platform. After all, the converted byte stream can be deserialized on other platforms to recover objects.

The thing is just that. It looks very simple, but there are still a lot of things behind. Please look down.


How are objects serialized?

However, Java currently does not have a keyword to directly define a so-called “persistent” object.

The persistence and anti persistence of objects need to be manually implemented by programmers in the codeExplicitlyActions to serialize and deserialize restore.

For example, if we want toStudentClass object is serialized to a class namedstudent.txtIn the text file, and then it is inversely sequenced through the text fileStudentClass object:

Serialization / deserialization, I've endured you for a long time

1. Student class definition

public class Student implements Serializable {

    private String name;
    private Integer age;
    private Integer score;
    
    @Override
    public String toString() {
        return "Student:" + '\n' +
        "name = " + this.name + '\n' +
        "age = " + this.age + '\n' +
        "score = " + this.score + '\n'
        ;
    }
    
    //... other omitted
}

2. Serialization

public static void serialize(  ) throws IOException {

    Student student = new Student();
    student.setName("CodeSheep");
    student.setAge( 18 );
    student.setScore( 1000 );

    ObjectOutputStream objectOutputStream = 
        new ObjectOutputStream( new FileOutputStream( new File("student.txt") ) );
    objectOutputStream.writeObject( student );
    objectOutputStream.close();
    
    System.out.println ("serialization succeeded! Student.txt file has been generated");
    System.out.println("==============================================");
}

3. Deserialization

public static void deserialize(  ) throws IOException, ClassNotFoundException {
    ObjectInputStream objectInputStream = 
        new ObjectInputStream( new FileInputStream( new File("student.txt") ) );
    Student student = (Student) objectInputStream.readObject();
    objectInputStream.close();
    
    System. Out. Println ("the deserialization result is:");
    System.out.println( student );
}

4. Operation results

Console printing:

Serialization succeeded! The student.txt file has been generated
==============================================
The deserialization result is:
Student:
name = CodeSheep
age = 18
score = 1000

What is the use of the serializable interface?

Defined aboveStudentClass, implements aSerializableInterface, however, when we click inSerializableInterface internal view, found itIt turned out to be an empty interface, does not contain any methods!

Serialization / deserialization, I've endured you for a long time

Imagine, if it’s defined aboveStudentClass forgot to addimplements SerializableWhat happens when?

The experimental result is that the program runs at this timeWill report an error, and throwNotSerializableExceptionException:

Serialization / deserialization, I've endured you for a long time

We follow the error prompt from the source code toObjectOutputStreamofwriteObject0()When you look at the bottom of the method, you suddenly realize:

Serialization / deserialization, I've endured you for a long time

If an object is neithercharacter stringarrayenumerationAnd it didn’t happenSerializableInterface, it will be thrown during serializationNotSerializableExceptionAbnormal!

Oh, I see!

originalSerializableThe interface is just a tag!!!

It tells the code as long as it is implementedSerializableInterface classes can be serialized! However, the real serialization action does not need to be completed by it.


serialVersionUIDWhat’s the use of number?

I’m sure you often see the following code lines defined in some classes, that is, a code namedserialVersionUIDFields for:

private static final long serialVersionUID = -4392658638228508589L;

Do you know the meaning of this statement? Why do we have a project calledserialVersionUIDYour serial number?

Continue to do a simple experiment and take the aboveStudentClass, for example, we don’t explicitly declare one in itserialVersionUIDField.

Let’s call the above firstserialize()Method, aStudentObject serialized to on local diskstudent.txtFile:

public static void serialize() throws IOException {

    Student student = new Student();
    student.setName("CodeSheep");
    student.setAge( 18 );
    student.setScore( 100 );

    ObjectOutputStream objectOutputStream = 
        new ObjectOutputStream( new FileOutputStream( new File("student.txt") ) );
    objectOutputStream.writeObject( student );
    objectOutputStream.close();
}

Next we areStudentClass, for example, add another name calledstudentIDField indicating student ID:

Serialization / deserialization, I've endured you for a long time

At this time, we take the data that has just been serialized locallystudent.txtThe file is also deserialized with the following code to try to restore the previous oneStudentObject:

public static void deserialize(  ) throws IOException, ClassNotFoundException {
    ObjectInputStream objectInputStream = 
        new ObjectInputStream( new FileInputStream( new File("student.txt") ) );
    Student student = (Student) objectInputStream.readObject();
    objectInputStream.close();
    
    System. Out. Println ("the deserialization result is:");
    System.out.println( student );
}

Run discoveryWrong reportAnd threwInvalidClassExceptionException:

Serialization / deserialization, I've endured you for a long time

The message here is very clear: before and after serializationserialVersionUIDIncompatible number!

From this place, at leastTwoImportant information:

  • 1. SerialVersionUID is a unique identifier before and after serialization
  • 2. Default if not explicitly definedserialVersionUID, the compiler will automatically declare one for it!

Question 1: serialVersionUIDSerialization ID can be regarded as a “secret code” in the process of serialization and deserialization. During deserialization, the JVM will compare the serial number ID in the byte stream with the serial number ID in the serialized class. Only when they are consistent can they be deserialized again, otherwise an exception will be reported to terminate the deserialization process.

Question 2:If when defining a serializable class, no one explicitly defines one for itserialVersionUIDIf so, the Java runtime environment will automatically generate a default for the class according to all aspects of the class informationserialVersionUID, once the structure or information of the class is changed as above, theserialVersionUIDIt will change!

So, forserialVersionUIDThe certainty of writing code is still recommendedimplements SerializableThe best thing is to explicitly declare one for itserialVersionUIDClear value!

Of course, if you don’t want to assign values manually, you can also use the automatic addition function of the IDE, such as the one I useIntelliJ IDEA, pressalt + enterYou can automatically generate and add classesserialVersionUIDField, very convenient:

Serialization / deserialization, I've endured you for a long time


Two special cases

  • 1. All bystaticDecorated fields are not serialized
  • 2. All bytransientFields decorated with modifiers are also not serialized

For the first point, because serialization savesState of the objectNot the state of the class, so it will be ignoredstaticThe static domain is also taken for granted.

For the second point, you need to knowtransientThe role of modifiers.

When serializing an object of a class, you do not want a field to be serialized (for example, this field stores privacy values, such as:passwordWait), then you can use it at this timetransientModifier to decorate the field.

For example, defined beforeStudentClass, add aPassword field, but you don’t want to serialize totxtText, you can:

Serialization / deserialization, I've endured you for a long time

This is serializationStudentClass object,passwordThe field is set to the default valuenull, this can be seen from the result of deserialization:

Serialization / deserialization, I've endured you for a long time


Controlled and enhanced serialization

Binding blessing

From the above process, we can see that the process of serialization and deserialization is actuallyFlawed, because there is an intermediate process from serialization to deserialization. If someone gets the intermediate byte stream and forges or tampers with it, the deserialized object will have a certain risk.

After all, deserialization is also equivalent to“Implicit” object constructionTherefore, we want to do this during deserializationControlledObject deserialization action.

How about a controlled method?

The answer is:Self writtenreadObject()Function for the deserialization construction of an object to provide constraints.

Since I wrote it myselfreadObject()Function, you can do many controllable things: such as various judgment work.

Also with the aboveStudentClass as an example, generally speaking, students’ grades should be0 ~ 100In order to prevent students’ test scores from being tampered with into a wonderful value by others during deserialization, we can write it ourselvesreadObject()Function to control deserialization:

private void readObject( ObjectInputStream objectInputStream ) throws IOException, ClassNotFoundException {

    //Call the default deserialization function
    objectInputStream.defaultReadObject();

    //Manually check the validity of students' scores after deserialization. If any problem is found, terminate the operation!
    if( 0 > score || 100 < score ) {
        Throw new illegalargumentexception ("student scores can only be between 0 and 100!");
    }
}

For example, I deliberately changed the student’s score to101At this time, the deserialization is terminated immediately and an error is reported:

Serialization / deserialization, I've endured you for a long time

For the above code, some small partners may wonder why it is customizedprivateofreadObject()Methods can be called automatically, which requires you to follow the underlying source code to explore. I helped you follow itObjectStreamClassAt the bottom of the class, I’m sure you will suddenly realize:

Serialization / deserialization, I've endured you for a long time

It’s the reflection mechanism at work again! Yes, in Java, sure enough, everything can be “reflected” (funny), even if it is defined in a classprivatePrivate methods can also be pulled out and implemented, which is simply comfortable.

Singleton mode enhancement

An easily overlooked problem is:Serializable singleton classes may not be singleton

A small code example is clear.

For example, here we use it firstjavaWrite a common “static inner class” Singleton mode implementation:

public class Singleton implements Serializable {

    private static final long serialVersionUID = -1576643344804979563L;

    private Singleton() {
    }

    private static class SingletonHolder {
        private static final Singleton singleton = new Singleton();
    }

    public static synchronized Singleton getSingleton() {
        return SingletonHolder.singleton;
    }
}

Then write a validation main function:

public class Test2 {

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

        ObjectOutputStream objectOutputStream =
                new ObjectOutputStream(
                    new FileOutputStream( new File("singleton.txt") )
                );
        //Serialize the singleton object into the text file singleton.txt
        objectOutputStream.writeObject( Singleton.getSingleton() );
        objectOutputStream.close();

        ObjectInputStream objectInputStream =
                new ObjectInputStream(
                    new FileInputStream( new File("singleton.txt") )
                );
        //Deserialize the object in the text file singleton.txt to singleton1
        Singleton singleton1 = (Singleton) objectInputStream.readObject();
        objectInputStream.close();

        Singleton singleton2 = Singleton.getSingleton();

        //The running result actually prints false!
        System.out.println( singleton1 == singleton2 );
    }

}

After running, we found that:The deserialized singleton object is not equal to the original singleton objectNo doubt, this has not achieved our goal.

The solution is: handwriting in singleton classreadResolve()Function to directly return a singleton object to avoid it:

private Object readResolve() {
    return SingletonHolder.singleton;
}

Serialization / deserialization, I've endured you for a long time

In this way, when deserializing an object read from the stream,readResolve()Will be called to deserialize the newly created object instead of the returned object.


little does one think

I thought this article would be finished soon, but I pulled out so many things. However, after combing and connecting in series, I still felt a lot clearer.

That’s it. See you next.

The GitHub open source project: github.com/hansonwang99/javacollection has been included in this article. There are detailed self-study programming learning routes, interview questions and experience, programming materials and a series of technical articles. The resources are constantly updated


Slower, faster

Recommended Today

Supervisor

Supervisor [note] Supervisor – H view supervisor command help Supervisorctl – H view supervisorctl command help Supervisorctl help view the action command of supervisorctl Supervisorctl help any action to view the use of this action 1. Introduction Supervisor is a process control system. Generally speaking, it can monitor your process. If the process exits abnormally, […]