This article is participating in “Java Theme Month – Java Development in Action”, see the activity link for details

Serialization is keeping an object on disk or allowing it to be transferred directly over the network. The object serialization mechanism allows Java objects in memory to be converted into platform-independent binary streams that persist such binaries on disk. Other programs once have access to this binary stream. You can restore the binary stream to its original Java object


Object serialization: Writing a Java object to an IO stream, and deserializing an object to recover the object from an IO stream.

If an object is to be saved to disk or transferred over a network, the class should implement either the Serializable interface or the Externalizable interface.

Serializable is very simple to implement with Serializable. You simply have the target class implement the Serializable tag interface without implementing any methods.


Once a class implements the Serializable interface, its objects can be serialized.

package cn.lvkang.com.gradletest; import java.io.BufferedWriter; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.OutputStreamWriter; import java.io.Serializable; public class SerTest implements Serializable { private int age = 25; Private String name =" private String name "; private SerTest() { System.out.println(this.age+"-----------"+this.name); } public static void main(String[] args) {try {ObjectOutputStream fos = new ObjectOutputStream(new FileOutputStream("object.txt")); SerTest ser = new SerTest(); fos.writeObject(ser); } catch (Exception e) { e.printStackTrace(); } try {ObjectInputStream ois = new ObjectInputStream(new FileInputStream("object.txt")); SerTest serTest = (SerTest) ois.readObject(); System.out.println(serTest.age); System.out.println(serTest.name); } catch (Exception e) { e.printStackTrace(); }}}Copy the code

It is important to note that only the data of the Java object is deserialized, not the Java class, so the deserialization used to restore the Java object must provide the class file of the class to which the Java object belongs, otherwise it will throw a ClassNotFoundException

Also, SerTest has only one constructor, and only one print statement in the constructor. When deserializing, you don’t see the program calling the constructor, suggesting that the deserialization mechanism doesn’t need a constructor to initialize Java objects.


Serialization of object references

If a member variable of a class is not a primitive or String type but a reference type, the reference class must be serializable, otherwise the class that owns the reference variable is also non-serializable.

As follows:

class Person implements Serializable { String name; SerTest test; public Person(String name, SerTest test) { this.name = name; this.test = test; }}Copy the code

Person holds a reference to a SerTest, which can only be serialized if SerTest is serializable,

Assume the following scenario:

SerTest test = new SerTest(); Person p1 = new Person(" Person ",test); Person p2 = new Person(" 表 ",test);Copy the code

There is a problem. When serializing P1, the system will serialize the test object referenced by P1. If p2 is serialized, the system will sequence P2 and serialize test. This causes P1 and P2 to use different objects, which clearly defeats the purpose of Java serialization. So Java uses a special algorithm. The algorithm content is as follows:

  • All objects saved on disk have a serialized number.
  • When the program view serializes an object, the program will first say whether the object has been serialized or not, and only if the object has never been serialized will the system convert the object to a sequence of bytes and output it
  • If the object has already been serialized, the program simply outputs a serialization number, not a re-serialization of the object.

If p2 is serialized, test has already been serialized. If p2 is serialized, test has already been serialized.


Custom serialization:

In some special cases, if a class contains a particular kind of information, such as bank account information, you do not want to serialize the instance variable value, or you do not want to recursively serialize the instance traversal because a variable of the class is not serialized.

When an object serialization, the system will automatically combine the object of all the instance variables in serialization, if an instance of the class variable references to other classes of objects, is the object of reference will be serialized, if the referenced object instance variable references to other classes, also on the referenced object is instantiated, this situation is called a recursive serialization.

By using the TRANSIENT keyword modifier in front of the instance variable, you can specify that the instance variable is ignored for Java serialization. As shown below.

public class SerTest implements Serializable { private transient int age = 25; Private String name =" private String name "; private SerTest() { System.out.println(this.age+"-----------"+this.name); } public static void main(String[] args) {try {ObjectOutputStream fos = new ObjectOutputStream(new) FileOutputStream("object.txt")); SerTest ser = new SerTest(); fos.writeObject(ser); } catch (Exception e) { e.printStackTrace(); } try {ObjectInputStream ois = new ObjectInputStream(new FileInputStream("object.txt")); SerTest serTest = (SerTest) ois.readObject(); System.out.println(serTest.age); System.out.println(serTest.name); } catch (Exception e) { e.printStackTrace(); }}}Copy the code

After transient is added, the class’s age property will not be serialized. Similarly, when deserializing, age has no value, i.e., 0.

Encrypt sensitive fields:

During serialization, the virtual machine attempts to call the writeObject and readOjbect methods in the object. Without such methods, DefaultWriteObject of ObjectOutputStream and defaultReadObject of ObjectInputStream are called by default. User-defined writeObject and readObject methods allow users to control the serialization process. For example, the array of serialization can be dynamically changed in the process of serialization. Based on this principle, it can be used in practical applications, and sensitive fields can be encrypted.

public class SerTest implements Serializable { private static ObjectOutputStream fos; private static ObjectInputStream ois; Private int age = 28; Private String name = "private String name "; Private SerTest() {} private void writeObject(ObjectOutputStream fos) throws IOException { System. Out. Println (" -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- in the encryption -- -- -- -- -- -- -- -- -- "); System.out.println(" original name: "+this.name); System.out.println(" old age: "+this.age); StringBuffer buffer = new StringBuffer(this.name); // Reverse the name fos.writeObject(buffer.reverse()); Fos.writeint (this.age*10*(5-2)); } // Call private void readObject(ObjectInputStream ois) throws IOException on deserialization. ClassNotFoundException {System. Out. Println (" -- -- -- -- -- -- -- -- -- -- -- -- -- -- encrypted -- -- -- -- -- -- -- -- -- -- -- -- -- -- "); StringBuffer buffer = (StringBuffer) ois.readObject(); this.name = buffer.toString(); System.out.println(" encrypted name: "+this.name); this.age = ois.readInt(); System.out.println(" age: "+this.age); } public static void main(String[] args) { try { SerTest test = new SerTest(); fos = new ObjectOutputStream(new FileOutputStream("obj.obj")); fos.writeObject(test); // serialize fos.close(); ois = new ObjectInputStream(new FileInputStream("obj.obj")); // Deserialize and decrypt sensitive fields SerTest t = (SerTest) ois.readObject(); System. Out. Println (" -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- of -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "); StringBuffer buffer = new StringBuffer(t.name); System.out.println(" decrypted name: "+buffer.reverse().toString()); System.out.println(" age after decrypting: "+(t.age/10/(5-2))); ois.close(); } catch (IOException e) { e.printStackTrace(); } catch (ClassNotFoundException e) { e.printStackTrace(); }}}Copy the code

Custom serialization is used in the application, where ObjectOutputStream /ObjectInputStream is reflected based on the object you pass in. Determine if you have written the writeObject/readObject method. If it does, it calls what you wrote, otherwise it calls the default method.

== encrypts the data inside the object when it is serialized, and decrypts it when it is retrieved. This is the custom serialization of the object ==


Static variable serialization:

Serialization stores the state of the object, not the state of the class. Static variables are state of the class, so static constants are not stored when serialized.


WriteReplace method:

There is also a more thorough customization mechanism, which can even convert the object to another object when it is self-serialized. WriteReplace will be called by the serialization mechanism as long as the method exists. As follows:

public class SerTest implements Serializable { private static ObjectOutputStream fos; private static ObjectInputStream ois; Private int age = 28; Private String name = "private String name "; private SerTest() { } private Object writeReplace() throws ObjectStreamException { System.out.println(" serialized..............") ); ArrayList<Object> list = new ArrayList<>(); List. Add (new Person(" lI si ")); return list; } public static void main(String[] args) { try { SerTest test = new SerTest(); fos = new ObjectOutputStream(new FileOutputStream("obj.obj")); fos.writeObject(test); // serialize fos.close(); ois = new ObjectInputStream(new FileInputStream("obj.obj")); ArrayList t = (ArrayList) ois.readObject(); System.out.println(t.get(0).toString()); ois.close(); } catch (IOException e) { e.printStackTrace(); } catch (ClassNotFoundException e) { e.printStackTrace(); } } } class Person implements Serializable { String name; public Person(String name) { this.name = name; } @Override public String toString() { return name; }}Copy the code

The print result is as follows

In serialization.............. Li siCopy the code

Before serializing an object, the system calls the object’s WriteReplace method. If that method returns another object, the writeReplace method of that other object is called until that method does not return another object. The program finally calls the object’s writeObject() method to save the state of the object. = =

As you can see from the printed results, what looks like a serialized SerTest when serialized is actually an ArrayList. A Person object has been added to the ArrayList, which also implements the serialization interface. But the writeReplace method is not implemented. So writeObject will finally be called to save the state of the object.

Note: The above collection holds the Person object and does not have a writeReplace method, so the writeObject() method will be called to save the state of the object. But if you save objects of the current class (objects of the SerTest class), this will cause recursion, and the program will simply hang. == because SerTest objects have writeReplace methods. The program will keep calling this method. Eventually, the program will simply die. As follows:

Modify the writeReplace method

Private Object writeReplace() throws ObjectStreamException {system.out.println (" serialization.............." ); ArrayList<Object> list = new ArrayList<>(); // list.add(new Person(" lI si ")); list.add(new SerTest()); return list; }Copy the code

The print result is as follows:

. In serialization.............. In serialization.............. Exception in thread "main" java.lang.StackOverflowError at sun.nio.cs.UTF_8.updatePositions(UTF_8.java:77) at sun.nio.cs.UTF_8.access$200(UTF_8.java:57) at sun.nio.cs.UTF_8$Encoder.encodeArrayLoop(UTF_8.java:636) at sun.nio.cs.UTF_8$Encoder.encodeLoop(UTF_8.java:691) at java.nio.charset.CharsetEncoder.encode(CharsetEncoder.java:579) at sun.nio.cs.StreamEncoder.implWrite(StreamEncoder.java:271) at sun.nio.cs.StreamEncoder.write(StreamEncoder.java:125) .Copy the code

Report a stack exception directly.


ReadResolve () method:

This method corresponds to the writeReplace method. This method is called immediately after readObject(). The return value of this method replaces the deserialized object, which is immediately discarded.


Externalizable serialization mechanism, which is entirely up to the programmer to store and restore object data. To use Externalizable, you must implement this interface.

This interface defines two methods.

  • writeExternal():

Classes that need serialization implement the writeExternal() method to store the state of the object. This method calls the DataOutput method (which is the parent of ObjectOutput) to hold the value of the primitive instance variable, and the writeObject() method of ObjectOutput to hold the value of the reference instance variable.

  • ReadExternal:

The serialized class is required to implement the readExternal method for deserialization. This method calls DataInput(which is ObjectInput’s parent interface) to restore the value of the instance variable of the base type and ObjectInput’s readObject() method to restore the value of the instance variable of the reference type.

In fact, implementing the Externalizable interface is a lot like custom serialization, except that it is enforced. As follows:

public class ExterTest implements Externalizable { private static ObjectOutputStream out; private static ObjectInputStream in; Private String name =" "; private int age = 35; Public ExterTest(){system.out.println (" I am a constructor "); } @Override public void writeExternal(ObjectOutput out) throws IOException { out.writeObject(new StringBuffer(this.name).reverse()); out.writeInt(this.age); } @Override public void readExternal(ObjectInput in) throws ClassNotFoundException, IOException { this.name = ((StringBuffer)in.readObject()).reverse().toString(); this.age = in.readInt(); } public static void main(String[] args){ ExterTest test = new ExterTest(); try { out = new ObjectOutputStream(new FileOutputStream("test.obj")); out.writeObject(test); out.close(); in = new ObjectInputStream(new FileInputStream("test.obj")); ExterTest t = (ExterTest) in.readObject(); System.out.println(t.name+"-------"+t.age); in.close(); } catch (IOException e) { e.printStackTrace(); } catch (ClassNotFoundException e) { e.printStackTrace(); }}}Copy the code

The above program implements the Externalizable interface and also implements two methods that have the same body except for their names and readOjbect()/writeObject().

If your program needs to serialize an object that displays the Externalizable interface, you can also call the writeObject() method of OabjectOutputStream.

Note that when deserializing in this way, the program first creates an instance using the public no-argument constructor. The readExternal() method is then executed to deserialize, so the interface implementing Externalizable must provide a public no-argument constructor.

There are a few other things to note about object serialization:

  • The object’s class name, power variables (including primitive data types, arrays, and references to other objects) are serialized. Methods, class variables, and TRANSIENT instance variables are not serialized.
  • Classes that implement Serializable interfaces that require an instance variable to be unserialized append the transient modifier instead of the static keyword to the instance variable.
  • Ensure that the instance variable type of the serialized object can also be serialized, otherwise you need to use the TRANSIENT keyword to modify the instance variable. Otherwise the class cannot be serialized.
  • Deserialization has a class file for the serialized object.
  • When serialized objects are read from a file or network, they must be read in the order in which they are actually written.

Version:

As mentioned above, deserialization must have the class file of the object. Now the problem is that as the project is upgraded, the system class file will also be upgraded. How can Java ensure the compatibility of the two classes?

  • The Java serialization mechanism allows a private static final serialVersionUID value for a serialized class, whose variables are used to represent the serialized version of the Java class. That is, when a class is upgraded, the serialization mechanism will treat them as the same serialized version as long as the values of the class variables are not changed.

  • It is best to add the value of the private Static Final Long serialVersionUID class variable to each class to be serialized. The class is then modified and the object can be serialized.

If the value is not explicitly defined, the value of the class variable will be calculated by the JVM, and the value of the modified class is often different from the value of the unmodified class, which can easily cause the object to be deserialized due to version problems.


If this article is helpful to your place, we are honored, if there are mistakes and questions in the article, welcome to put forward!