This article describes object serialization and deserialization in Java. Share it for everyone for your reference. The details are as follows:
1. Introduction
Object serialization refers to the process of converting an object into a sequence of bytes, while deserialization is the process of restoring an object based on a sequence of bytes.
Serialization is generally used in the following scenarios:
1. Save the object permanently and save the byte sequence of the object to the local file;
2. Pass objects in the network by serializing them;
3. Pass objects between processes through serialization.
The class to which the object belongs must implement the Serializable or Externalizable interface to be serialized. For classes that implement Serializable interface, the serialization and deserialization adopt the default serialization method. The Externalizable interface inherits the Serializable interface interface and is an extension of Serializable. The Externalizable interface class completely controls serialization and Deserialization behavior.
Java.io.ObjectOutputStream represents the object output stream, and its method writeObject(Object obj) can realize the serialization of the object and write the obtained byte sequence to the target output stream.
Java.io.ObjectInputStream represents the object input stream, and its readObject() method can read a sequence of bytes from the source input stream, deserialize it into an object, and return it.
2. Several ways to serialize
Suppose that a Customer class is defined, depending on the customer's serialization method, there may be the following serialization methods:
1. Implement Serializable, undefined readObject and writeObject methods
ObjectOutputStream uses JDK to serialize non-transient instance variables of Customer object by default;
ObjectInputStream deserializes non-transient instance variables of Customer object using JDK default method.
2. Implement Serializable and define readObject and writeObject methods
ObjectOutputStream calls the writeObject(ObjectOutputStream out) method of the Customer class to serialize non-transient instance variables of the Customer object;
ObjectInputStream calls the readObject(ObjectInputStream in) method of the Customer class to deserialize non-transient instance variables of the Customer object.
3. Implement Externalizable, define readExternal and writeExternal methods
ObjectOutputStream calls the writeExternal method of the Customer class to serialize non-transient instance variables of the Customer object;
ObjectInputStream first instantiates an object through the parameterless constructor of the Customer class, and then deserializes the non-transient instance variable of the Customer object using the readExternal method.
3. Serializable interface
The class enables its serialization functionality by implementing the java.io.Serializable interface. Classes that do not implement this interface will not be able to serialize or deserialize any of their states. All subtypes of a serializable class are themselves serializable. The serialization interface has no methods or fields and is only used to identify serializable semantics.
During the deserialization process, fields of the non-serialized class will be initialized using the common or protected parameterless constructor of the class. The serializable subclass must be able to access the parameterless constructor. Fields that can be serializable subclasses will be restored from the stream.
When traversing a class view, you may encounter objects that do not support the Serializable interface. In this case, a NotSerializableException is thrown and the class that is not serializable is identified.
1. Accurate signature
Classes that require special processing during serialization and deserialization must implement special methods using the following accurate signatures:
private void writeObject(java.io.ObjectOutputStream out) throws IOException
private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException;
private void readObjectNoData() throws ObjectStreamException;
The writeObject method is responsible for writing the state of an object of a specific class so that the corresponding readObject method can restore it. The default mechanism for saving the fields of the Object can be called by calling out.defaultWriteObject. The method itself does not need to involve states belonging to its superclass or subclass. The state can be saved by writing individual fields to the ObjectOutputStream using the writeObject method or using the method supported by DataOutput for basic data types.
The readObject method is responsible for reading and restoring class fields from the stream. It can call in.defaultReadObject to call the default mechanism to restore non-static and non-transitory fields of the object. The defaultReadObject method uses information in the stream to allocate fields of objects in the stream that are saved through the corresponding specified fields in the current object. This is used to deal with situations where new fields need to be added after class evolution. The method itself does not need to involve states belonging to its superclass or subclass. The state can be saved by writing individual fields to the ObjectOutputStream using the writeObject method or using the method supported by DataOutput for basic data types.
In the case where the serialization stream does not list the given class as the superclass to be deserialized, the readObjectNoData method is responsible for initializing the object state of a specific class. This occurs when the deserialized instance class used by the receiver is different from the sender and the class extended by the receiver version is not a class extended by the sender version. It also occurs when the serialization stream has been tampered with; therefore, the readObjectNoData method can be used to correctly initialize the deserialized object, regardless of whether the source stream is "hostile" or incomplete.
When writing objects to a stream, you need to specify the serializable class of the alternative object to be used, and you should implement this special method with an accurate signature:
ANY-ACCESS-MODIFIER Object writeReplace() throws ObjectStreamException;
This writeReplace method will be called by serialization, provided that if this method exists, and it can be accessed by a method defined in the class of the serialized object. Therefore, the method can have private, protected, and package-private access. The subclass access to this method follows the java access rules.
When reading an instance of a class from a stream, you need to specify the exact signature that the alternate class should use to implement this special method.
ANY-ACCESS-MODIFIER Object readResolve() throws ObjectStreamException;
This readResolve method follows the same calling rules and access rules as writeReplace.
If a class defines a readResolve method, then the readResolve method will be called at the end of the deserialization, and the object returned by the method is the final result of the deserialization.
2.serialVersionUID
The serialization runtime uses a version number called serialVersionUID to associate with each serializable class, which is used during deserialization to verify that the sender and receiver of the serialized object loaded for the object. Serialize compatible classes. If the receiver loads the serialVersionUID of the object's class different from the corresponding sender's version number, deserialization will result in an InvalidClassException. A serializable class can explicitly declare its own serialVersionUID by declaring a field named "serialVersionUID" (that field must be a static, final long field):
ANY-ACCESS-MODIFIER static final long serialVersionUID = 42L;
If the serializable class does not explicitly declare serialVersionUID, the serialization runtime calculates the default serialVersionUID value of the class based on various aspects of the class, as described in the "Java(TM) Object Serialization Specification". However, it is strongly recommended that all serializable classes explicitly declare serialVersionUID values, because the calculation of the default serialVersionUID is highly sensitive to the details of the class, and may vary greatly depending on the compiler implementation, so in the deserialization process An unexpected InvalidClassException may result. Therefore, to ensure consistency between serialVersionUID values across different java compilers, the serialized class must declare an explicit serialVersionUID value. It is also strongly recommended to use the private modifier to display the declaration serialVersionUID if possible because such declarations are only used to directly declare the class -- the serialVersionUID field as an inherited member is of no use. Array classes cannot declare an explicit serialVersionUID, so they always have the default calculated value, but the array classes do not match the serialVersionUID value requirements.
3.Externalizable interface
Externalizable is an extension of Serailizable. The serialization of the class that implements the Externalizable interface has the following characteristics:
Call the class's method writeExternal during serialization, and deserialize the readExternal method;
When performing deserialization, the class's parameterless constructor is called first, which is different from the default deserialization. Therefore, for classes that implement the Externalizable interface to implement serialization, a public parameterless constructor must be provided. , otherwise an exception will appear during deserialization.
Fourth, summary
If the default serialization method is adopted, as long as a class implements the Serializable interface, its instance can be serialized. Generally, classes designed specifically for inheritance should try not to implement the Serializable interface, because once the parent class implements the Serializable interface, all its subclasses are serializable.
The shortcomings of the default serialization method:
1. It is not safe to directly serialize sensitive data that is not suitable for disclosure of the object;
2. It will not check whether the member variables of the object meet the correct constraints, and may be tampered with the data and cause abnormal operation;
3. Recursive traversal of the object graph is required. If the object graph is very complicated, it will consume a lot of resources and set up to cause stack overflow of the Java virtual machine;
4. Make the class's interface constrained by the internal implementation of the class, restricting the class's upgrade and maintenance.
By implementing the private type writeObject() and readObject() of the Serializable interface, or implementing the Externalizable interface, implementing the writeExternal() and readExternal() methods, and providing a public-type parameterless constructor Two ways to control the serialization process It can effectively avoid the shortcomings of the default serialization method.
It is hoped that this article is helpful to everyone's Java program design.