Java serialization and deserialization

Java serialization and deserialization

Reprinted in https://www.cnblogs.com/9dragon/p/10901448.html

1, Meaning, meaning and usage scenario of serialization

Serialization: writing objects to IO streams
Deserialization: recovering objects from IO streams
Meaning: the serialization mechanism allows the Java objects that realize serialization to be converted into bit byte sequences. These byte sequences can be saved on disk or transmitted through the network to restore to the original objects in the future. The serialization mechanism enables the object to exist independently from the operation of the program.
Usage scenario: all objects that can be transmitted on the network must be serializable, such as RMI (remote method invoke). The incoming parameters or returned objects are serializable, otherwise an error will occur; All java objects that need to be saved to disk must be serializable. It is generally recommended that each JavaBean class created by the program implement the serializable interface.

2, Serialization implementation

If an object needs to be saved to disk or transmitted over the network, this class should implement one of the Serializable interface or Externalizable interface.

1,Serializable
1.1 ordinary serialization
The Serializable interface is a tag interface without implementing any methods. Once this interface is implemented, the objects of this class are Serializable.

Serialization steps:
Step 1: create an ObjectOutputStream output stream;

Step 2: call writeObject of ObjectOutputStream object to output serializable object.

public class Person implements Serializable {
  private String name;
  private int age;
  //I do not provide parameterless constructors
  public Person(String name, int age) {
      this.name = name;
      this.age = age;
  }

  @Override
  public String toString() {
      return "Person{" +
              "name='" + name + '\'' +
              ", age=" + age +
              '}';
  }
}
public class WriteObject {
  public static void main(String[] args) {
      try (//Create an ObjectOutputStream output stream
           ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("object.txt"))) {
          //Serialize object to file s
          Person person = new Person("9 Loong", 23);
          oos.writeObject(person);
      } catch (Exception e) {
          e.printStackTrace();
      }
  }
}

Deserialization steps:
Step 1: create an ObjectInputStream input stream;

Step 2: call readObject() of ObjectInputStream object to get the serialized object.

We serialize the above to person The person object of TXT is deserialized back

public class Person implements Serializable {
  private String name;
  private int age;
  //I do not provide parameterless constructors
  public Person(String name, int age) {
      System.out.println("Deserialization, did you call me?");
      this.name = name;
      this.age = age;
  }

  @Override
  public String toString() {
      return "Person{" +
              "name='" + name + '\'' +
              ", age=" + age +
              '}';
  }
}
public class ReadObject {
  public static void main(String[] args) {
      try (//Create an ObjectInputStream input stream
           ObjectInputStream ois = new ObjectInputStream(new FileInputStream("person.txt"))) {
          Person brady = (Person) ois.readObject();
          System.out.println(brady);
      } catch (Exception e) {
          e.printStackTrace();
      }
  }
}

//Output results
//Person{name = '9 dragons', age=23}
waht??? The output tells us that deserialization does not call the constructor. The object of inverse sequence is the object generated by the JVM itself, which is not generated by the construction method.

1.2 members are serialization of references
If the member of a serializable class is not a basic type or a String type, the reference type must also be serializable; Otherwise, this class cannot be serialized.

Let's add an example of Teacher. Remove the code that implements the Serializable interface of Person.

public class Person{
    //Omit related properties and methods
}
public class Teacher implements Serializable {

    private String name;
    private Person person;

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

     public static void main(String[] args) throws Exception {
        try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("teacher.txt"))) {
            Person person = new Person("Monkey D Luffy", 20);
            Teacher teacher = new Teacher("Raleigh", person);
            oos.writeObject(teacher);
        }
    }
}

We can see that the program directly reports an error because the object of the Person class is not serializable, which leads to the non serializability of the Teacher object

1.3 mechanism of serializing the same object multiple times
If the same object is serialized multiple times, will this object be serialized multiple times? The answer is No.

public class WriteTeacher {
    public static void main(String[] args) throws Exception {
        try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("teacher.txt"))) {
            Person person = new Person("Monkey D Luffy", 20);
            Teacher t1 = new Teacher("Raleigh", person);
            Teacher t2 = new Teacher("Red hair shanks", person);
            //Write four objects to the input stream in turn
            oos.writeObject(t1);
            oos.writeObject(t2);
            oos.writeObject(person);
            oos.writeObject(t2);
        }
    }
}

Serialize t1, t2, person and t2 objects into the file teacher Txt file.

Note: the order of deserialization is the same as that of serialization.

public class ReadTeacher {
    public static void main(String[] args) {
        try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream("teacher.txt"))) {
            Teacher t1 = (Teacher) ois.readObject();
            Teacher t2 = (Teacher) ois.readObject();
            Person p = (Person) ois.readObject();
            Teacher t3 = (Teacher) ois.readObject();
            System.out.println(t1 == t2);
            System.out.println(t1.getPerson() == p);
            System.out.println(t2.getPerson() == p);
            System.out.println(t2 == t3);
            System.out.println(t1.getPerson() == t2.getPerson());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

//Output results
//false
//true
//true
//true
//true

From the output results, it can be seen that Java serializes the same object and will not serialize this object multiple times to get multiple objects.

Java serialization algorithm
All objects saved to disk have a serialization code number
Only when the object has been serialized to a byte by the virtual machine will the program check whether the object has been serialized before.
If this object has been serialized, you can output the number directly.

Illustrates the above serialization process.

1.4 potential problems of Java serialization algorithm
Because the java sequencing algorithm will not serialize the same object repeatedly, it will only record the number of serialized objects. If after serializing a variable object (the content in the object can be changed), the content of the object is changed and serialized again, the object will not be converted into a byte sequence again, but only the serialization number will be saved.

public class WriteObject {
    public static void main(String[] args) {
        try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("person.txt"));
             ObjectInputStream ios = new ObjectInputStream(new FileInputStream("person.txt"))) {
            //Serialize person for the first time
            Person person = new Person("9 Loong", 23);
            oos.writeObject(person);
            System.out.println(person);

            //Modify name
            person.setName("One Piece");
            System.out.println(person);
            //Serialize person for the second time
            oos.writeObject(person);

            //p1 and p2 were sequenced in reverse order
            Person p1 = (Person) ios.readObject();
            Person p2 = (Person) ios.readObject();
            System.out.println(p1 == p2);
            System.out.println(p1.getName().equals(p2.getName()));
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
//Output results
//Person{name='9 ', age=23}
//Person{name = 'pirate king', age=23}
//true
//true

1.5 optional custom serialization
Sometimes, we have the requirement that some attributes do not need to be serialized. Use the transient keyword to select fields that do not need to be serialized.

public class Person implements Serializable {
   //No serialization of name and age is required
   private transient String name;
   private transient int age;
   private int height;
   private transient boolean singlehood;
   public Person(String name, int age) {
       this.name = name;
       this.age = age;
   }
   //Omit the get and set methods
}
public class TransientTest {
   public static void main(String[] args) throws Exception {
       try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("person.txt"));
            ObjectInputStream ios = new ObjectInputStream(new FileInputStream("person.txt"))) {
           Person person = new Person("9 Loong", 23);
           person.setHeight(185);
           System.out.println(person);
           oos.writeObject(person);
           Person p1 = (Person)ios.readObject();
           System.out.println(p1);
       }
   }
}
//Output results
//Person{name='9 ', age=23', singlehood=true', height=185cm}
//Person{name='null', age=0', singlehood=false', height=185cm}

From the output, we can see that the attribute modified by transient will be ignored during java serialization, so the attribute modified by transient is the default value of the object deserialized. For reference type, the value is null; Basic type, value is 0; boolean type, the value is false.

Using transient is simple, but it completely isolates this property from serialization. java provides optional custom serialization. You can control the way of serialization, or encode and encrypt the serialized data.

private void writeObject(java.io.ObjectOutputStream out) throws IOExceptionï¼›
private void readObject(java.io.ObjectIutputStream in) throws IOException,ClassNotFoundException;
private void readObjectNoData() throws ObjectStreamException;

By overriding the writeObject and readObject methods, you can choose which attributes need to be serialized and which attributes do not. If the writeObject is serialized with some rule, the corresponding readObject needs to be deserialized with the opposite rule so that the object can be deserialized correctly. Here we show the reverse encryption of names.

public class Person implements Serializable {
   private String name;
   private int age;
   //Omit the constructor, get and set methods

   private void writeObject(ObjectOutputStream out) throws IOException {
       //Write name inversion to binary stream
       out.writeObject(new StringBuffer(this.name).reverse());
       out.writeInt(age);
   }

   private void readObject(ObjectInputStream ins) throws IOException,ClassNotFoundException{
       //Reverse and recover the read string
       this.name = ((StringBuffer)ins.readObject()).reverse().toString();
       this.age = ins.readInt();
   }
}

When the serialized stream is incomplete, the readobjectnodata () method can be used to properly initialize the deserialized object. For example, when different classes are used to receive deserialized objects, or the serialization stream is tampered with, the system will call the readObjectNoData() method to initialize the deserialized objects.

More thorough custom serialization

ANY-ACCESS-MODIFIER Object writeReplace() throws ObjectStreamException;
ANY-ACCESS-MODIFIER Object readResolve() throws ObjectStreamException;

writeReplace: when serializing, this method will be called first, and then the writeObject method. This method replaces the target serialized object with any object

public class Person implements Serializable {
  private String name;
  private int age;
  //Omit construction methods, get and set methods

  private Object writeReplace() throws ObjectStreamException {
      ArrayList<Object> list = new ArrayList<>(2);
      list.add(this.name);
      list.add(this.age);
      return list;
  }

   public static void main(String[] args) throws Exception {
      try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("person.txt"));
           ObjectInputStream ios = new ObjectInputStream(new FileInputStream("person.txt"))) {
          Person person = new Person("9 Loong", 23);
          oos.writeObject(person);
          ArrayList list = (ArrayList)ios.readObject();
          System.out.println(list);
      }
  }
}

//Output results
//[9 dragon, 23]
readResolve: replace the deserialized object during deserialization, and the deserialized object will be discarded immediately. This method is called after readeObject.

public class Person implements Serializable {
    private String name;
    private int age;
    //Omit construction methods, get and set methods
     private Object readResolve() throws ObjectStreamException{
        return new ("brady", 23);
    }
    public static void main(String[] args) throws Exception {
        try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("person.txt"));
             ObjectInputStream ios = new ObjectInputStream(new FileInputStream("person.txt"))) {
            Person person = new Person("9 Loong", 23);
            oos.writeObject(person);
            HashMap map = (HashMap)ios.readObject();
            System.out.println(map);
        }
    }
}

//Output results
//{brady=23}
readResolve is often used to reverse the sequence of singleton classes to ensure the uniqueness of singleton classes.

Note: the access modifiers of readResolve and writeReplace can be private, protected and public. If the parent class overrides these two methods, the subclasses need to be rewritten according to their own needs, which is obviously not a good design. It is generally recommended to override the readResolve method for the class modified by final. There is no problem; Otherwise, override readResolve and use the private modifier.

2. Externalizable: force custom serialization
By implementing the Externalizable interface, the writeExternal and readExternal methods must be implemented.

public interface Externalizable extends java.io.Serializable {
     void writeExternal(ObjectOutput out) throws IOException;
     void readExternal(ObjectInput in) throws IOException, ClassNotFoundException;
}
public class ExPerson implements Externalizable {

    private String name;
    private int age;
    //Note that the pulic parameterless constructor must be added
    public ExPerson() {
    }

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

    @Override
    public void writeExternal(ObjectOutput out) throws IOException {
        //Reverse the name and write it to the binary stream
        StringBuffer reverse = new StringBuffer(name).reverse();
        System.out.println(reverse.toString());
        out.writeObject(reverse);
        out.writeInt(age);
    }

    @Override
    public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
        //Invert the read string and assign it to the name instance variable
        this.name = ((StringBuffer) in.readObject()).reverse().toString();
        System.out.println(name);
        this.age = in.readInt();
    }

    public static void main(String[] args) throws IOException, ClassNotFoundException {
        try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ExPerson.txt"));
             ObjectInputStream ois = new ObjectInputStream(new FileInputStream("ExPerson.txt"))) {
            oos.writeObject(new ExPerson("brady", 23));
            ExPerson ep = (ExPerson) ois.readObject();
            System.out.println(ep);
        }
    }
}
//Output results
//ydarb
//brady
//ExPerson{name='brady', age=23}

Note: the Externalizable interface is different from the Serializable interface. To implement this interface, you must implement two methods in the interface to realize user-defined serialization, which is mandatory; In particular, you must provide a pulic parameterless constructor, because you need to create objects by reflection during deserialization.

3. Comparison of two serialization methods

Implement Serializable interfaceImplement Externalizable interface
The system automatically stores the necessary informationProgrammers decide what information to store
Java built-in support, easy to implement, just need to implement the interface, without any code supportTwo methods within the interface must be implemented
Slightly poor performanceSlightly better performance

Although the Externalizable interface has brought some performance improvement, it also increases the complexity, so it is generally serialized by implementing the Serializable interface.

3, serialVersionUID serial version number

We know that deserialization must have class files, but class files will be upgraded as the project is upgraded. How can serialization ensure compatibility before and after upgrading?

java serialization provides a serialization version number of private static final long serialVersionUID. Only the version number is the same. Even if the serialization attribute is changed, the object can be deserialized correctly.

public class Person implements Serializable {
    //Serialized version number
    private static final long serialVersionUID = 1111013L;
    private String name;
    private int age;
    //Omit the construction method and get,set
}

If the version number of the class used in deserialization is inconsistent with that used in serialization, the deserialization will report an InvalidClassException exception.

The serialization version number can be specified freely. If it is not specified, the JVM will calculate a version number according to the class information. In this way, with the upgrade of class, it will not be able to deserialize correctly; Another obvious hidden danger of not specifying the version number is that it is not conducive to the migration between JVMs. The class file may not be changed, but the calculation rules of different JVMs may be different, which will also lead to failure of deserialization.

Under what circumstances do you need to modify serialVersionUID? There are three cases.

1) If only the method is modified and the deserialization cannot be affected, there is no need to modify the version number;
2) If only static variables and transient variables (variables modified by transient) are modified, the deserialization will not be affected and the version number does not need to be modified;
3) If non transient variables are modified, deserialization may fail. If the type of the instance variable in the new class is inconsistent with that of the class during serialization, deserialization will fail. At this time, you need to change the serialVersionUID. If only instance variables are added, the default value will be added after deserialization; If the instance variable is reduced, the reduced instance variable will be ignored during deserialization.

4, Summary

1. All objects requiring network transmission need to implement serialization interface. It is suggested that all JavaBeans implement Serializable interface.
2. The class name and instance variables of the object (including basic types, arrays and references to other objects) will be serialized; Methods, class variables, and transient instance variables are not serialized.
3. If you want a variable not to be serialized, use the transient modifier.
4. The reference type member variable of the serialized object must also be serializable, otherwise an error will be reported.
5. There must be a class file for the serialized object when deserializing.
6. When reading serialized objects through files and networks, they must be read in the order of actual writing.
7. For the serialization of singleton classes, the readResolve() method needs to be overridden; Otherwise, the singleton principle will be broken.
8. The same object is serialized multiple times. Only the first time is serialized into a binary stream. In the future, only the serialization number is saved, and the serialization will not be repeated.
9. It is recommended to add the serial uid number of all items for easy upgrading.

Keywords: Java Back-end

Added by palpie on Thu, 03 Mar 2022 04:16:42 +0200