Serializable1.1 Ordinary serialization 1.2 Members are serialized by reference 1.3 Mechanism of serializing the same object multiple times 1.4 Potential problems of Java serialization algorithm 1.5 Optional custom serialization 2, Externalizable: mandatory custom serialization 3, comparison of the two serialization 3, serialVersionUID 4, summary
I. The meaning, significance and application scenarios of serialization
- Serialization: Writes an object to an IO stream
- Deserialization: Recover objects from AN IO stream
- Meaning: The serialization mechanism allows serialized Java objects to be converted into bit-byte sequences that can be stored on disk or transferred over the network for later restoration to the original object. Serialization allows objects to exist independently of program execution.
- Usage scenarios: All objects that can be transferred over the network must be serializable, such as RMI (Remote Method Invoke). The parameters passed in or objects returned 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 every JavaBean class created by a program implement the Serializeable interface.
Second, the way serialization is implemented
If an object needs to be saved to disk or transferred over a network, the class should implement either the Serializable interface or the Externalizable interface.
1, the Serializable
1.1 Common Serialization
The Serializable interface is a markup interface that does not implement any methods. Once this interface is implemented, the objects of this class are serializable.
- Serialization steps:
-
Step 1: Create an ObjectOutputStream;
-
Step 2: Call writeObject of the ObjectOutputStream object to print the serializable object.
public class Person implements Serializable {
private String name;
private int age;
// I do not provide a no-argument constructor
public Person(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString(a) {
return "Person{" +
"name='" + name + '\' ' +
", age=" + age +
'} ';
}
}
public class WriteObject {
public static void main(String[] args) {
try (// Create an ObjectOutputStream
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("object.txt"))) {
// Serialize the object to file s
Person person = new Person("Nine dragon".23);
oos.writeObject(person);
} catch (Exception e) {
e.printStackTrace();
}
}
}
Copy the code
- Deserialization steps:
-
Step 1: Create an ObjectInputStream.
-
Step 2: Call ObjectInputStream’s readObject() to get the serialized object.
We deserialize back the Person object serialized to Person.txt above
public class Person implements Serializable {
private String name;
private int age;
// I do not provide a no-argument constructor
public Person(String name, int age) {
System.out.println("Deserialize, did you call me?");
this.name = name;
this.age = age;
}
@Override
public String toString(a) {
return "Person{" +
"name='" + name + '\' ' +
", age=" + age +
'} ';
}
}
public class ReadObject {
public static void main(String[] args) {
try (// Create an ObjectInputStream
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("person.txt"))) {
Person brady = (Person) ois.readObject();
System.out.println(brady);
} catch (Exception e) {
e.printStackTrace();
}
}
}
// Output the result
/ / Person {name = 'nine dragons' age = 23}
Copy the codewaht???? The output tells us that deserialization does not call the constructor. Antisequence objects are objects that are generated by the JVM itself, not by constructors.
1.2 Members are serialized references
If the member of a serializable class is not a primitive type or String, the reference type must also be serializable; Otherwise, the class cannot be serialized.
Let’s add a Teacher class. Remove Person from implementing the Serializable interface code.
public class Person{
// omit related attributes 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("The road".20);
Teacher teacher = new Teacher("雷利", person);
oos.writeObject(teacher);
}
}
}
Copy the code
We see a direct error because the object of the Person class is not serializable, which results in the Teacher object not being serializable
1.3 Mechanism for serializing the same object more than once
Will the same object be serialized more than once? 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("The road".20);
Teacher t1 = new Teacher("雷利", person);
Teacher t2 = new Teacher("Redheaded Shanks.", person);
// Write four objects in turn to the input stream
oos.writeObject(t1);
oos.writeObject(t2);
oos.writeObject(person);
oos.writeObject(t2);
}
}
}
Copy the code
Serialize the T1, T2, person, and T2 objects into the teacher.txt file.
Note: The order of deserialization is the same as when it was serialized.
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 the result
//false
//true
//true
//true
//true
Copy the code
As you can see from the output, Java does not serialize the same object more than once to get multiple objects.
- Java serialization algorithm
-
All objects saved to disk have a serialization code number
-
When the program attempts to serialize an object, it first checks to see if the object has been serialized, and only serializes the object as byte sequence output if the object has never been serialized (on this virtual machine).
-
If the object has already been serialized, just print the number.
Figure out the serialization process above.
1.4 Potential problems with the Java serialization algorithm
Because the Java ordering algorithm does not re-serialize the same object, only the number of the serialized object is recorded. If you serialize a mutable object (the contents of the object can be changed) and then change the contents of the object and serialize it again, you do not convert the object to a sequence of bytes again, but only save the serialization number.
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("Nine dragon".23);
oos.writeObject(person);
System.out.println(person);
/ / modify the name
person.setName(One Piece);
System.out.println(person);
// Serialize person a second time
oos.writeObject(person);
// Deserialize p1 and p2
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 the result
/ / Person {name = 'nine dragons' age = 23}
//Person{name=' one Piece ', age=23}
//true
//true
Copy the code
1.5 Optional custom serialization
-
Sometimes, there is a requirement that certain attributes do not need to be serialized. Use the TRANSIENT keyword to select fields that do not need serialization.
public class Person implements Serializable {
// There is no need to serialize the name and age
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,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("Nine dragon".23);
person.setHeight(185);
System.out.println(person);
oos.writeObject(person);
Person p1 = (Person)ios.readObject();
System.out.println(p1);
}
}
}
// Output the result
//Person{name='9 ', age=23, singlehood=true, height=185cm}
//Person{name='null', age=0', singlehood=false', height=185cm}
Copy the codeAs we can see from the output, Java will ignore this field when serializing a transient property, so the deserialized object, the transient property is the default value. For reference types, the value is null; Basic type, value 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 serialization is performed, or encode and encrypt the serialized data.
private void writeObject(java.io.ObjectOutputStream out) throwsIOException.
private void readObject(java.io.ObjectIutputStream in) throws IOException,ClassNotFoundException;
private void readObjectNoData(a) throws ObjectStreamException;
Copy the codeBy overriding the writeObject and readObject methods, you can choose which attributes need to be serialized and which do not. If writeObject is serialized using one rule, the corresponding readObject needs to be deserialized using the opposite rule in order to deserialize the object correctly. Reverse encryption of names is shown here.
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 the name reversed to the binary stream
out.writeObject(new StringBuffer(this.name).reverse());
out.writeInt(age);
}
private void readObject(ObjectInputStream ins) throws IOException,ClassNotFoundException{
// Reverse the string read back
this.name = ((StringBuffer)ins.readObject()).reverse().toString();
this.age = ins.readInt();
}
}
Copy the codeThe readObjectNoData() method can be used to properly initialize the deserialized object when the serialized stream is incomplete. For example, when a deserialized object is received with a different class, or when the serialized stream is tampered with, the readObjectNoData() method is called to initialize the deserialized object.
-
More thorough custom serialization
ANY-ACCESS-MODIFIER Object writeReplace() throws ObjectStreamException;
ANY-ACCESS-MODIFIER Object readResolve() throws ObjectStreamException;-
WriteReplace: On serialization, this method is called first, followed by the writeObject method. This method can replace any object with the target serialized object
public class Person implements Serializable {
private String name;
private int age;
// omit the constructor, get and set methods
private Object writeReplace(a) 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("Nine dragon".23);
oos.writeObject(person);
ArrayList list = (ArrayList)ios.readObject();
System.out.println(list);
}
}
}
// Output the result
/ / [23] nine dragons,
Copy the code -
ReadResolve: Replace the deserialized object during deserialization. The deserialized object is immediately discarded. This method is called after readeObject.
public class Person implements Serializable {
private String name;
private int age;
// omit the constructor, get and set methods
private Object readResolve(a) 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("Nine dragon".23);
oos.writeObject(person);
HashMap map = (HashMap)ios.readObject();
System.out.println(map);
}
}
}
// Output the result
//{brady=23}
Copy the codeReadResolve is often used to unsequence a singleton class to ensure that the singleton class is unique.
Note that readResolve and writeReplace access modifiers can be private, protected, or public. If the parent class overrides these two methods, the subclass needs to override them, which is obviously not a good design. It is generally recommended that the readResolve method be overridden for final modified classes without problem; Otherwise, override readResolve to use the private modifier.
-
Externalizable: Mandatory custom serialization
To implement 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;
}
Copy the code
public class ExPerson implements Externalizable {
private String name;
private int age;
// Note that the pulic no-argument constructor must be added
public ExPerson(a) {
}
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 string read 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 the result
//ydarb
//brady
//ExPerson{name='brady', age=23}
Copy the code
Note: The Externalizable interface is different from the Serializable interface. Implementing this interface must implement two methods in the interface to implement custom serialization, which is mandatory. In particular, pulic’s no-argument constructor must be provided because reflection is required to create objects at deserialization time.
3. Comparison of the two serials
Implement the Serializable interface | Implement the Externalizable interface |
---|---|
The system automatically stores the necessary information | The programmer decides what information to store |
Java built-in support, easy to implement, just need to implement the interface, no code support | Two methods within the interface must be implemented |
Performance of slightly | Performance is slightly better |
Although the Externalizable interface provides some performance improvements, it also increases the complexity of the transformation, so Serializable interface is generally implemented for serialization.
Serialization version number serialVersionUID
We know that deserialization must have a class file, but as the project is upgraded, the class file is also upgraded. How can serialization ensure compatibility before and after upgrade?
Java serialization provides a serialization version number for private Static Final Long serialVersionUID. Only the version number is the same, and the object can be deserialized correctly even if the serialization property is changed.
public class Person implements Serializable {
// Serialize the version number
private static final long serialVersionUID = 1111013L;
private String name;
private int age;
// omit constructor and get,set
}
Copy the code
If the version number of the class used for deserialization is not the same as that used for deserialization, deserialization will report an InvalidClassException.
The serialization version number is optional. If not, the JVM calculates a version number based on the class information. Another obvious pitfall of not specifying a version number is that it is not good for porting between JVMS. It is possible that the class file has not changed, but different JVMS may evaluate different rules, which also makes deserialization impossible.
When do you need to change the serialVersionUID? There are three cases.
- If only the method is changed and deserialization is unaffected, there is no need to change the version number;
- If only the static variable is modified, the transient variable (transient variable), deserialization is not affected, do not need to modify the version number;
- If a non-transient variable is modified, deserialization may fail. If the type of the instance variable in the new class is different from the type of the serialized class, deserialization will fail and you need to change the serialVersionUID. If you just add instance variables, deserialize back to add default values; If instance variables are reduced, deserialization ignores the reduced instance variables.
Four,
- All objects requiring network transport need to implement serialization interface, by suggesting that all Javabeans implement Serializable interface.
- The object’s class name and instance variables (including primitive types, arrays, and references to other objects) are serialized; Methods, class variables, and TRANSIENT instance variables are not serialized.
- If you want a variable not to be serialized, use the transient modifier.
- The reference type member variable of the serialized object must also be serializable; otherwise, an error is reported.
- Deserialization must have a class file for the serialized object.
- When serialized objects are read from files or networks, they must be read in the order in which they are actually written.
- Serialization of singleton classes requires overriding the readResolve() method; Otherwise, you break the singleton principle.
- The same object is serialized multiple times, only the first serialization to the binary stream, the subsequent only save serialization number, will not be repeated serialization.
- It is recommended that all serializable classes have the serialVersionUID version number to facilitate project upgrades.