Serializability in Java
It's a mechanism in Java to ensure that the objects of classes implementing java.io.Serializable interface will have the capability of storing their states on a persistent storage that can be loaded back into the memory with the same state whenever needed.
The Serializable interface is only a marker interface and has no methods or fields in it. It just serves the purpose of notifying the JVM about the Class implementing it that the Class may require to save its state on a persistent medium and subsequently may need to restore the same saved state in the memory when needed. The compiler handles it either by identifying serialVersionUID field in the class or by adding one to the class (the next post talks in detail about serialVersionUID) and presence of this field notifies the Runtime Environment to treat the instance creation appropriately.
The subclasses of a Serializable class are automatically Serializable, and if you want to Serialize sub classes of non-serialized classes then you need to ensure that the super class has a no-argument constructor. Reason being, on marking the sub class as a Serialized class, it tries to save and restore the state of public, protected, and package (of course only if accessible) fields of the super class also. The sub class can do this only if the super class has a no-argument constructor. Otherwise, you'll get a runtime exception.
While De-Serialization also the state of the public, protected, and package (only if accessible) fields of the non-serialized super classes are restored using the no-argument constructor of the super class. The state of the fields of the serialized sub-class is restored from the stream.
Custom handling of objects while Serialization/Deserialization
In addition to the default serialization or deserialization of objects, Java also supports special handling of the serialization (or deserialization) of objects. You just need to implement the following three special methods in that case and do whatever way you want the save/restore of the objects to go. These special methods are:-
- private void writeObject(java.io.ObjectOutputStream out) throws IOExceptionprivate
- private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundExceptionprivate
- private void readObjectNoData() throws ObjectStreamException
As the name suggests, this method is used for writing the state of the object to the output stream passed as its parameter. Usually the defaultWriteObject() method (or methods from the DataOutput interface for primitive types) of the ObjectOutputStream class is used to write non-static and non-transient fields of the current class to the output stream. The defaultWriteObject() method can be called from within a writeObject method only, otherwise it throws NotActiveException. Some I/O error while writing the date to the stream will cause the IOException to be thrown by this method.
This method reads the data saved by the writeObject method and restores the state of the object. This method normally calls readDefaultObject() method (or methods from DataInput interface for primitive types) of the ObjectInputStream class to restore the non-static and non-transient fields of the object.
An interesting scenario: Suppose you have a class having 4 non-static and non-transient fields namely fld1, fld2, fld3, and fld4. Now you create an instance and save the state of the object on a persistent medium using writeObject method. Down the line the class evolves and you need to add two new non-static, non-transient fields namely fld5, fld6. What will happen, if you try to restore the previously saved state of the object with an object reference of the new version of the Class?
Well... nothing serious. Actually the readDefaultObject() method reads the data and the field name from the stream and assigns the corresponding named field of the current object. So, in our case fld1, fld2, fld3, and fld4 will get restored from the stream and the other two fields fld5 and fld6 will continue having default values.
Suppose you have a class named Class1 and you have saved the state of an object of this class on a persistent medium. You send that saved state to some other application, which has a different version of the same class 'Class1'. Say the recipient application is having a version where Class1 extends another class named 'Class2'. Now, if you try to restore the saved state shipped to the recipient application, you need to use readObjectNoData method and not the readObject method. Reason being, the recipient application will try to look for the state of the superclass of Class1, which is Class2 and obviously it won't find that in the saved state. This may happen in that case also where the saved state of the object gets tempered. readObjectNoData method simply initializes the state of the superclass in any of the above two scenarios.
Read Next - What is Externalizable interface in Java? Once done with this artcile you may like to compare the two interfaces and see the differences between the two. This may help you understanding when to use which one of the two interfaces? What are the potential problems and security risks with using these interfaces? ... The artcile - Serializability vs Externalizability in Java covers these topics.