Abstract: This article mainly introduces you to the serialization and deserialization of objects in java. The article introduces detailed sample codes. I hope it can be helpful to you.

This article is shared from the HUAWEI cloud community "What is serialization and deserialization in ", original author: dayu_dls.

This article mainly introduces you to the related content of the serialization and deserialization of objects in java. The article introduces the detailed sample code and hopes to be helpful to you.

1. What is serialization used 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 the memory after the program runs, and transforming it into bytes Sequences are also more convenient for network transportation and dissemination, so they are conceptually well understood:

  • serialization: converts Java objects into byte sequences.
  • deserialization: the byte sequence to the original Java object.

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

2. How is the object serialized?

In Java, if an object wants to achieve serialization, it must implement one of the following two interfaces:

  • Serializable interface
  • Externalizable interface

How do these two interfaces work? What is the relationship between the two? We will introduce them separately.

2.1 Serializable interface

If an object wants to be serialized, its class must implement this interface or its sub-interfaces.

All attributes of this object (including private attributes and the objects it references) can be serialized and deserialized to save and transfer. Fields that do not want to be serialized can be modified with transient.

Since the Serializable object is constructed entirely on the basis of the binary bits it stores, no constructor is called, so the Serializable class does not need a default constructor, but when the parent class of the Serializable class does not implement the Serializable interface, the deserialization process will Call the default constructor of the parent class, so the parent class must have a default constructor, otherwise an exception will be thrown.

Although it is simple and convenient to use the transient keyword to prevent serialization, the attributes modified by it are completely isolated from the serialization mechanism, resulting in the inability to obtain the value of the attribute during deserialization, and through the need to serialize the object Adding the writeObject() method and readObject() method to the Java class can control how to serialize each attribute, or even not serialize some attributes at all or encrypt and serialize some attributes.

2.2 Externalizable interface

It is a subclass of the Serializable interface. The writeExternal() and readExternal() methods that users need to implement are used to determine how to serialize and deserialize.

Because the serialization and deserialization methods need to be implemented by themselves, you can specify which attributes to serialize, and transient is invalid here.

When deserializing an Externalizable object, the no-parameter construction method of the class will be called first, which is different from the default deserialization method. If you delete the constructor without parameters of the class, or set the access rights of the constructor to private, default or protected, it will throw java.io.InvalidException: no valid constructor exception, so the Externalizable object must have a default construction Function, and must be public.

2.3 Comparison

When using, you only want to hide one attribute, such as the password pwd of the user object user. If you use Externalizable, and every attribute except pwd is written in the writeExternal() method, this is troublesome, you can use the Serializable interface, and It can be realized by adding transient in front of the attribute pwd to be hidden. If you want to define a lot of special processing, you can use Externalizable.

Of course, we have some doubts here. The writeObject() method and readObject() method in Serializable can implement custom serialization, while the writeExternal() and readExternal() methods in Externalizable can also be used. What are the similarities and differences?

  • The two methods of readExternal() and writeExternal(), except for the method signature and the method signature of readObject() and writeObject() are different, the method bodies of these two methods are exactly the same.
  • It should be pointed out that when using the Externalizable mechanism to deserialize the object, the program will use the public parameterless constructor to create an instance, and then execute the readExternal() method for deserialization, so the serialization class that implements Externalizable must provide public The parameterless structure.
  • Although the implementation of the Externalizable interface can bring a certain performance improvement, the implementation of the Externalizable interface leads to an increase in programming complexity, so most of the time the implementation of the Serializable interface is used to achieve serialization.

3. How does Serializable serialize objects?

3.1 Serializable demo

However, Java currently does not have a keyword that can directly define a so-called "persistent" object.

And persistent anti-persistent object by programmers need to manually code explicitly serialization and deserialization restore operation.

For example, if we want to serialize the Student class object to a text file named student.txt, and then deserialize it into a Student class object through the text file:

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'
        ;
    }
 
    // ... 其他省略 ...
}

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("序列化成功!已经生成student.txt文件");
    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("反序列化结果为:");
    System.out.println( student );
}

4. Running results

Console print:

序列化成功!已经生成student.txt文件
==============================================
反序列化结果为:
Student:
name = CodeSheep
age = 18
score = 1000

3.2 What is the use of Serializable interface?

When the Student class was defined above, a Serializable interface was implemented. However, when we clicked into the Serializable interface to view, it turned out to be an empty interface and did not contain any methods!

Just imagine, what happens if you forget to add implements Serializable when defining the Student class above?

The result of the experiment is that the program running at this time will report an error and throw a NotSerializableException:
image.png

We followed the error message from the source code all the way to the bottom of the writeObject0() method of ObjectOutputStream, and then we realized:
image.png

If an object is neither a string, an array, or an enumeration, nor does it implement the Serializable interface, a NotSerializableException will be thrown during serialization!

It turns out that the Serializable interface is only used as a mark! ! ! It tells the code that any class that implements the Serializable interface can be serialized! However, the real serialization action does not need to be completed by it.

3.3 What is the use of serialVersionUID number?

I believe you must often see the following lines of code defined in some classes, that is, a field named serialVersionUID is defined:

private static final long serialVersionUID = -4392658638228508589L;

Do you know the meaning of this statement? Why make a serial number named serialVersionUID?

Let's continue to do a simple experiment, and take the Student class above as an example. We did not explicitly declare a serialVersionUID field in it.

We first call the serialize() method above to serialize a Student object to the student.txt file on the local disk:

Next, let's do something in the Student class, for example, add a field named id to indicate the student ID:

public class Student implements Serializable {
    private String name;
    private Integer age;
    private Integer score;
    private Integer id;

At this time, we take the student.txt file that has been serialized to the local just now, and deserialize it with the following code, trying to restore the Student object just now:

Run and found that an error was reported, and an InvalidClassException was thrown
image.png

The information prompted here is very clear: the serialVersionUID numbers before and after serialization are not compatible!

At least two important messages can be drawn from this place:

1. serialVersionUID is a unique identifier before and after serialization

2. By default, if no one has explicitly defined serialVersionUID, the compiler will automatically declare one for it!

Question 1: serialVersionUID serialization ID can be regarded as a "secret code" in the process of serialization and deserialization. When deserializing, JVM will put the serial number ID in the byte stream and the serialized class The serial number ID is compared, and only if the two are consistent, can the deserialization be re-deserialized, otherwise an exception will be reported to terminate the deserialization process.

Question 2: If no one explicitly defines a serialVersionUID when defining a serializable class, the Java runtime environment will automatically generate a default serialVersionUID for it based on various aspects of the class. , Once the structure or information of the class is changed as above, the serialVersionUID of the class will also change!

Therefore, in order to ensure the certainty of serialVersionUID, it is recommended when writing code that it is best to explicitly declare a serialVersionUID explicit value for any class that implements Serializable!

Of course, if you don’t want to assign values manually, you can also use the IDE’s automatic addition function. For example, I use IntelliJ IDEA. Press alt + enter to automatically generate and add the serialVersionUID field to the class, which is very convenient:

Two special cases

1. Fields modified by static will not be serialized

2. Any field modified by the transient modifier will not be serialized

For the first point, because serialization saves the state of the object rather than the state of the class, it is reasonable to ignore the static static field.

For the second point, you need to understand the role of the transient modifier.

If you do not want a field to be serialized when serializing an object of a certain class (for example, this field stores a privacy value, such as a password, etc.), then you can use the transient modifier to modify the field.

For example, in the previously defined Student class, add a password field, but do not want to serialize to txt text, you can:

public class Student implements Serializable {
    private static final long serialVersionUID = -4392658638228508589L;
    private transient String name;
    private Integer age;
    private Integer score;
    private transient String passwd;

In this way, when serializing the Student class object, the password field will be set to the default value of null, which can be seen from the result of deserialization:

public static void serialize() throws IOException {

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

image.png

4. Implement Externalizable

public UserInfo() {
    userAge=20;//这个是在第二次测试使用,判断反序列化是否通过构造器
}
public void writeExternal(ObjectOutput out) throws IOException  {
    //  指定序列化时候写入的属性。这里仍然不写入年龄
    out.writeObject(userName);
    out.writeObject(usePass);
}
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException  {
    // 指定反序列化的时候读取属性的顺序以及读取的属性
    // 如果你写反了属性读取的顺序,你可以发现反序列化的读取的对象的指定的属性值也会与你写的读取方式一一对应。因为在文件中装载对象是有序的
    userName=(String) in.readObject();
    usePass=(String) in.readObject();
}

When we serialize an object, because this class implements the Externalizable interface, which attributes can be serialized and which cannot be serialized are defined in the writeExternal() method, so the object can be serialized after passing through here. Serialize and save the file, do not process the ones that cannot be serialized, and then automatically call the readExternal() method when deserializing, read them one by one according to the sequence order, and automatically encapsulate them into objects to return, and then receive them in the test class to complete The reverse sequence.

The only feature of the Externalizable instance class is that it can be written to the serialization stream. This class is responsible for saving and restoring the instance content. If someone wants to fully control the stream format and content of an object and its supertype, it must implement the writeExternal and readExternal methods of the Externalizable interface. These methods must explicitly coordinate with the supertype to save its state. These methods will replace the custom writeObject and readObject methods.

  • writeExternal(ObjectOutput out)

The object can implement the writeExternal method to save its content, it can save its basic value by calling the DataOutput method, or calling the writeObject method of ObjectOutput to save the object, string, and array.

  • readExternal(ObjectInput in)

The object implements the readExternal method to restore its content, it restores its basic type by calling the DataInput method, and it calls readObject to restore the object, string, and array.

The difference between externalizable and Serializable:

1. Implementing the serializable interface is to serialize all attributes by default. If there are attributes that do not need to be serialized, use transient modification. The externalizable interface is a subclass of serializable. To implement this interface, you need to rewrite the writeExternal and readExternal methods to specify the properties of the object serialization and the behavior of reading the object properties from the serialization file.

2. The object serialization file that implements the serializable interface is deserialized without the construction method. What is loaded is a persistent state of the object of this class, and then this state is assigned to another variable of the class. The object serialization file that implements the externalizable interface is deserialized first through the construction method to obtain the control object, and then the readExternal method is called to read the content in the serialized file and assign the corresponding attribute.

5. Control and enhancement of serialization

5.1 Binding blessing

It can be seen from the above process that the process of serialization and deserialization is actually flawed, because there is an intermediate process from serialization to deserialization. If someone else gets the intermediate byte stream, then it is forged or Tampering, the deserialized object will have a certain risk.

After all, deserialization is also equivalent to an "implicit" object construction, so we hope to perform controlled object deserialization during deserialization.

So how is it controlled?

The answer is: Write your own readObject() function for the deserialization of the object, thereby providing constraints.

Since you write the readObject() function yourself, you can do a lot of controllable things: such as various judgments.

Take the Student class above as an example. Generally speaking, students' scores should be between 0 and 100. In order to prevent students' test scores from being tampered with by others when deserializing, we can write readObject() by ourselves. The function is used to control the deserialization:

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

    // 调用默认的反序列化函数
    objectInputStream.defaultReadObject();

    // 手工检查反序列化后学生成绩的有效性,若发现有问题,即终止操作!
    if( 0 > score || 100 < score ) {
        throw new IllegalArgumentException("学生分数只能在0到100之间!");
    }
}

For example, I deliberately changed the student's score to 101. At this time, the deserialization was immediately terminated and an error was reported:
image.png

For the above code, why the custom private readObject() method can be automatically called? Follow the underlying source code to find out. After following the bottom of the ObjectStreamClass class, the reflection mechanism is at work! Yes, in Java, everything can be "reflected" (funny), even the private methods defined in the class can be extracted and executed, which is simply a cause of comfort.

5.2 Singleton mode enhancement

An easily overlooked problem is: serializable singleton class may not be singleton!

A small code example will make it clear.

For example, here we first use java to write 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 verification main function:

public class Test2 {

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

        ObjectOutputStream objectOutputStream =
                new ObjectOutputStream(
                    new FileOutputStream( new File("singleton.txt") )
                );
        // 将单例对象先序列化到文本文件singleton.txt中
        objectOutputStream.writeObject( Singleton.getSingleton() );
        objectOutputStream.close();

        ObjectInputStream objectInputStream =
                new ObjectInputStream(
                    new FileInputStream( new File("singleton.txt") )
                );
        // 将文本文件singleton.txt中的对象反序列化为singleton1
        Singleton singleton1 = (Singleton) objectInputStream.readObject();
        objectInputStream.close();

        Singleton singleton2 = Singleton.getSingleton();

        // 运行结果竟打印 false !
        System.out.println( singleton1 == singleton2 );
    }

}

After running, we found that the singleton object after deserialization is not equal to the original singleton object, which undoubtedly did not reach our goal.

The solution is: hand-write the readResolve() function in the singleton class and directly return the singleton object:

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

import java.io.Serializable;

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;
    }
 
    private Object readResolve() {
        return SingletonHolder.singleton;
    }
}

In this way, when deserialization reads an object from the stream, readResolve() will be called, replacing the newly created object for deserialization with the returned object.

Click to follow to get to know the fresh technology of Huawei Cloud for the first time~


华为云开发者联盟
1.4k 声望1.8k 粉丝

生于云,长于云,让开发者成为决定性力量