1. Introduction
Serialization means to convert state of an object into a byte stream. Deserialization is the process of converting the serialized form of an object back into a copy of the object. A Singleton class is supposed to have only one instance. Using deserialization, it is possible to break this pattern. If you deserialize a serialized object, it will create a new instance and hence break the singleton pattern.
Lets see this with the help of an example. We’ll first create a singleton class.
import java.io.Serializable;
// A typical Singleton class
class Singleton implements Serializable {
public static Singleton instance = new Singleton();
private Singleton() {
// private constructor
}
}
We’ll now serialize the Singleton class we created and will then deserialize.
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInput;
import java.io.ObjectInputStream;
import java.io.ObjectOutput;
import java.io.ObjectOutputStream;
public class BreakSingletonExample {
public static void main(String[] args) {
try {
// instance1 created from Singleton
Singleton instance1 = Singleton.instance;
// serialize instance1
ObjectOutput out = new ObjectOutputStream(new FileOutputStream("file.text"));
out.writeObject(instance1);
out.close();
// deserialize from file to object
ObjectInput in = new ObjectInputStream(new FileInputStream("file.text"));
Singleton instance2 = (Singleton) in.readObject();
in.close();
System.out.println("instance1 hashCode: " + instance1.hashCode());
System.out.println("instance2 hashCode: " + instance2.hashCode());
}
catch (Exception e) {
e.printStackTrace();
}
}
}
When you run the code, the output would look like this:
instance1 hashCode: 1639705018 instance2 hashCode: 1867083167
As you can observe, hashCode of both instances is different which should not be the case if both instances were same. This proves that serialization can break the singleton pattern.
2. Solution
The solution is to implement the readResolve() method.
After the implementation the Singleton class looks like the following:
import java.io.Serializable;
// A typical Singleton class
class Singleton implements Serializable {
public static Singleton instance = new Singleton();
private Singleton() {
// private constructor
}
// implement readResolve method
protected Object readResolve() {
return instance;
}
}
Now run the BreakSingletonExample class again. The output would look like the following:
instance1 hashCode: 1639705018 instance2 hashCode: 1639705018
As you can observe, the hashcode of both the instances in the same.
3. The readResolve Method
According to the documentation:
For
SerializableandExternalizableclasses, thereadResolvemethod allows a class to replace/resolve the object read from the stream before it is returned to the caller. By implementing thereadResolvemethod, a class can directly control the types and instances of its own instances being deserialized.The
readResolvemethod is called whenObjectInputStreamhas read an object from the stream and is preparing to return it to the caller.ObjectInputStreamchecks whether the class of the object defines thereadResolvemethod. If the method is defined, thereadResolvemethod is called to allow the object in the stream to designate the object to be returned. The object returned should be of a type that is compatible with all uses. If it is not compatible, aClassCastExceptionwill be thrown when the type mismatch is discovered.
