Item 86: Implement Serializable with great caution

86. Be very careful when implementing Serializable

Cost of making it serializable

Serializing can be done in seconds, but it can be very costly in the long run.

Implementing Serializable loses the flexibility to change the implementation class once released

The fact that the class implements Serializable means that the byte stream encoded version (hereafter serialized form) becomes part of the published API. Once a class is published, it is required to support it forever. Also, in the non-customized default serialized form, private fields are also part of the public API, making the design that minimizes accessible fields (Item15) meaningless.

If you accept the default selialized form and later change the internal representation of the class, incompatible changes will occur. Problems occur when the client serializes an instance with an older version of the class and deserializes it with a newer version of the class. It is possible to do the conversion, but it is difficult and makes the code dirty. When implementing Serializable, it is necessary to consider long-term use and try to design a high-quality one (Item87,90).

An example of the restrictions imposed on the implementation of Serializable is the serial version UID. Every Serializable class has a serial version UID, a number to prove itself. (** It seems to identify whether the class version is different before and after restoration. **) (http://www.ne.jp/asahi/hishidama/home/tech/java/serial.html) If you don't define this as a static final long field, it will be automatically allocated at run time. This value is affected by the class name, members, etc., and if something is changed, this value will also change. If you don't define the UID and the compatibility is broken, an InvalidClassException will occur at runtime.

Implementing Serializable increases the potential for bugs and security holes

Deserialization is like an "invisible constructor". It's easy to forget that because it's invisible, you need to guarantee the invariants established by the constructor to prevent attackers from accessing the object you're building. Relying on the default deserialization mechanism can easily destroy or gain unauthorized access to an invariant.

Implementing Serializable increases the effort involved in releasing a new version of a class

When a class that implements Serializable is revised, it is necessary to check whether the instance of the new version can be serialized, the older version can be deserialized, and vice versa. Therefore, the number of test cases increases in proportion to the product of the number of versions and the serialized implementation class. Testing requirements can be reduced if the first edition of the Serializable implementation class is carefully designed and included (Item87,90).

Implementation of Serializable is not easy to decide

Implementing Serializable is essential for embedding classes in frameworks that use Java serialization for object transport and persistence. Also, the implementation of Serializable makes it easy to handle as a component from other classes. However, as mentioned above, there are various costs associated with implementing Serializable. When designing classes, they need to be weighed.

Historically, value classes such as the BigInteger and Instant classes implement Serializable, but rarely in classes that represent active entities such as thread pools.

Classes and interfaces designed for inheritance are basic, implement Serializable, should not be inherited

Breaking this rule puts a heavy burden on the subclasses of that class. As an exception to this rule, it is understandable that the classes and interfaces existing in the framework that require Serializable implementation for everything involved implement Serializable. Throwable and Component implement Serializable. Throwable implements Serializable, so RMI can throw exceptions from the server to the client. Since the Component implements Serializable, GUI elements are transported and saved. This feature wasn't used much during the heyday of Swing and AWT.

Be careful when implementing a class that is serializable and extensible and has fields

Subclasses should not override the finalize method if the instance field has any invariants. Otherwise there is a danger of finalizer attack (Item8).

Also, if the class invariant is broken by the initialization of the instance field, it is necessary to add the readObjectNoData method.

private void readObjectNoData()
     throws ObjectStreamException;

(** Mystery around here **)

Precautions when not serializable

Writing a serializable subclass is more tedious when you choose not to make it Serializable in your inheritance class. Normal deserialization of such classes requires a no-argument constructor that can access the superclass. If it cannot be prepared, use Serialization Proxy Pattern (Item90).

Inner class should not implement Serializable

The inner class uses synthetic fields. Synthetic fields are generated by the compiler and hold a reference to the enclosing instance and a reference to a local variable from the enclosing scope.

Since the format of serialization for synthetic fields is undefined, Serializable implementations in the inner class should be avoided. However, static member classes can be implemented in Serializable.

Recommended Posts

Item 86: Implement Serializable with great caution
Implement GraphQL with Spring Boot
Implement jCaptcha reload with ajax
Implement search function with form_with