Reputation: 258
My singleton class be like this:
public class SerializableSingleton implements Serializable {
private static SerializableSingleton instance = new SerializableSingleton();
private SerializableSingleton() {
System.out.println("Constructor is being called");
}
public static SerializableSingleton getInstance() {
return instance;
}
}
Now it's written on the web that when we deserialize this singleton object, it will give a new instance back and not the previous one, and in order to fix this, use the readResolve()
method.
But my question is - How is it even possible? When a static class member can't be serialized, how does the question of deserializing it comes at all? and it is all over the net?
Since the singleton object is static:
private static SerializableSingleton instance = new SerializableSingleton();
How is an instance getting serialized in the first place?
Upvotes: 0
Views: 291
Reputation: 379
As the docs say:
By implementing the readResolve method, a class can directly control the types and instances of its own instances being deserialized. The method is defined as follows:
ANY-ACCESS-MODIFIER Object readResolve() throws ObjectStreamException;
And:
... For example, a Symbol class could be created for which only a single instance of each symbol binding existed within a virtual machine. The readResolve method would be implemented to determine if that symbol was already defined and substitute the preexisting equivalent Symbol object to maintain the identity constraint. In this way the uniqueness of Symbol objects can be maintained across serialization.
And:
- A writeObject method to control what information is saved ...
- A readObject method [..] to read the information written ...
- A writeReplace method to allow a class to nominate a replacement object to be written to the stream
- A readResolve method to allow a class to designate a replacement object for the object just read from the stream
To get full control of Serialization (also to serialize static and transient fields) you have to implement the Externalization
interface:
The
writeExternal
andreadExternal
methods of theExternalizable
interface are implemented by a class to give the class complete control over the format and contents of the stream for an object and its supertypes.
So writeReplace
and readResolve
methods give you some more control than the ordinary (and automated) serialization mechanism, that is, replace the object before/after serialization. and it is not urgently linked to Singleton serialization, but to fulfill the proxy pattern for serialization. But as you mentioned, this mechanism is also used to implement serialization of a Singleton.
Upvotes: 0
Reputation: 15163
Serialization bypasses a lot of things in the language and even does some stuff that is not even possible with ordinary reflection.
When an object is serialized, it's class name and all instance fields (that are not transient
) are written to the stream.
The magic happens when deserializing an object.
private void readObject(ObjectInputStream)
method)readResolve()
method is called - if such a method exists. It's result is used as the result of deserializing the object.(This does not apply to record
s, instances of java.lang.Class
, java.lang.String
, java.lang.Enum
...)
In your example, this means that a new instance of your SerializableSingleton
is created - bypassing your private constructor and instead calling java.lang.Object.<init>()
- so you won't see the "Constructor is being called"
output.
Now you have two instances of an "Singleton". To restore the original singleton semantics (there only ever exists one single instance of that class), we replace the just-deserialized-instance with the canonical instance:
private Object readResolve() {
return getInstance();
}
tl;dr: Java's serialization is complicated and sometimes indistinguishable to black magic. Using java's serialization opens the door to some surprising behaviors.
Upvotes: 8