If you choose a regular serialized form, you have to think that you can't fix it in the future.
In general, you should choose the default serialized form only if the encoding results are almost the same as when you chose to design a custom serialized form. The default serialized form is an efficient encoding of the physical representation of the object graph. In other words
** The default serialized form is often appropriate when the physical representation of the object and the logical content match. ** ** For example, the following serialized form is suitable for a class that simply expresses a person's name.
// Good candidate for default serialized form
public class Name implements Serializable {
/**
* must be non-null
* @serial
*/
private final String lastName;
/**
* must be non-null
* @serial
*/
private final String firstName;
/**
* Middle name, or null if there is none
* @serial
*/
private final String middleName;
public Name(String lastName, String firstName, String middleName) {
super();
this.lastName = lastName;
this.firstName = firstName;
this.middleName = middleName;
}
}
The instance field of Name corresponds to the logical content.
** Even if you decide that the default serialized form is appropriate, you may need to provide a readObject method to ensure invariants and security. ** ** In the case of the above Name, the readObject method must ensure that lastName and firstName are not null. This will be described in detail in Items 88 and 90.
Although the lastName, firstName, and middleName of Name are private fields, they are documented. The reason is that these private fields are exposed as a serialized form of the class. If you add the tag @serial, you can create a dedicated page about serialized form with the generated Javadoc.
Consider a class that represents a list of strings as shown below, assuming that it is located in the exact opposite of the Name class.
//Must not be the default serialized form!
public final class StringList implements Serializable {
private int size = 0;
private Entry head = null;
private static class Entry {
String data;
Entry previous;
Entry next;
}
}
Logically, this class represents a sequence of strings. Physically, it represents a sequence as a bidirectional list. If you accept the default serialized form, the serialized form will reflect all the elements of the linked list and all the links between the entries.
** There are four disadvantages to using the default serialized form when there is a discrepancy between physical representation and logical content. ** **
A rational serialized form of a StringList is a list consisting of several strings. This structure is a logical representation of the data in a StringList. Below is a revised StringList using writeObject and readObject.
package tryAny.effectiveJava;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
//Improved StringList
public final class StringList2 implements Serializable {
private transient int size = 0;
private transient Entry head = null;
// No longer Serializable
private static class Entry {
String data;
Entry previous;
Entry next;
}
//Add a string to list
public final void add(String s) {
}
private void writeObject(ObjectOutputStream s) throws IOException {
s.defaultWriteObject();
s.writeInt(size);
for (Entry e = head; e != null; e = e.next) {
s.writeObject(e.data);
}
}
private void readObject(ObjectInputStream s) throws IOException, ClassNotFoundException {
s.defaultReadObject();
int numElements = s.readInt();
for (int i = 0; i < numElements; i++) {
add((String) s.readObject());
}
}
}
In the above class, writeObject first calls the defaultWriteObject method, and readObject first calls the defatlReadObject method. It is sometimes said that it is not necessary to call the defaultWriteObject method and defatlReadObject method when all the fields of the class are transient, but that is not the case and must be called. These calls make it possible to add non-transient fields in later releases while maintaining compatibility.
In terms of performance, when measured with a list of 10 10-character strings, the revised StringList has about half the amount of memory before, doubles the serialization speed, and eliminates the worry of stack overflow.
The physical implementation of the hash table is a series of hash values for the keys in hash buckets. The hash value of the key depends on the implementation, so using the default serialized form creates a serious bug.
transient Calling defaultWriteObject serializes all fields except those specified transiently, regardless of whether the default serialized form is used. Try to use the transient modifier as much as possible to avoid unnecessary serialization.
Fields marked transient have default default values when deserialized. In other words, it is null for reference type variables, 0 for int, and false for boolean. If that is unacceptable, either set it with the readObject method (Item88) or delay initialization when using it (Item83).
Since it is necessary to synchronize at the time of serialization, it is necessary to add synchronized to writeObject as well.
By making it explicit, source incompatibilities can be prevented (Item86). Also, since it is not necessary to generate the serialVersionUID at runtime, the performance will be slightly improved. When writing a new class, this number can be anything and does not have to be a unique value.
If you want to assign a UID to an existing class and maintain compatibility, you need to use an older version of the UID.