My straight question is: does it still make sense consider Enum for singleton implementation since Reflection is now limited?
By singleton implemented throw enum I mean some implementation like:
public enum SingletonEnum {
INSTANCE;
int value;
public int getValue() {
return value;
}
public void setValue(int value) {
this.value = value;
}
}
If we contrast the basic idea of modularity as mentioned in answer related to scope package access"... Jigsaw's accessibility rules now restrict access to public elements (types, methods, fields) only" and issue of reflexation fixed by enum we may wonder why still code singleton as enum.
Despite its simplicity, when serializing an enum, field variables are not getting serialized. On top of that enums do not support lazy loading.
To sum up, assuming I didn't say any foolish thing above, since the main advantage of using enum for singleton was protecting from reflection risks, I would reach the conclusion that coding a singleton as enum isn't anymore better than a simple implementation around static aproach like that:
When serialization is need
public class DemoSingleton implements Serializable {
private static final long serialVersionUID = 1L;
private DemoSingleton() {
// private constructor
}
private static class DemoSingletonHolder {
public static final DemoSingleton INSTANCE = new DemoSingleton();
}
public static DemoSingleton getInstance() {
return DemoSingletonHolder.INSTANCE;
}
protected Object readResolve() {
return getInstance();
}
}
When no serialization is involved neither is a complex object demanding lazy loading
public class Singleton {
public static final Singleton INSTANCE = new Singleton();
private Singleton() {}
}
*** EDITED: added after @Holger comment regard serialization
public class DemoSingleton implements Serializable {
private static final long serialVersionUID = 1L;
private DemoSingleton() {
// private constructor
}
private static class DemoSingletonHolder {
public static final DemoSingleton INSTANCE = new DemoSingleton();
}
public static DemoSingleton getInstance() {
return DemoSingletonHolder.INSTANCE;
}
protected Object readResolve() {
return getInstance();
}
private int i = 10;
public int getI() {
return i;
}
public void setI(int i) {
this.i = i;
}
}
public class DemoSingleton implements Serializable {
private volatile static DemoSingleton instance = null;
public static DemoSingleton getInstance() {
if (instance == null) {
instance = new DemoSingleton();
}
return instance;
}
private int i = 10;
public int getI() {
return i;
}
public void setI(int i) {
this.i = i;
}
}
It’s not clear why you think that enum
types were not lazily initialized. There is no difference to other class types:
public class InitializationExample {
public static void main(String[] args) {
System.out.println("demonstrating lazy initialization");
System.out.println("accessing non-enum singleton");
Object o = Singleton.INSTANCE;
System.out.println("accessing the enum singleton");
Object p = SingletonEnum.INSTANCE;
System.out.println("q.e.d.");
}
}
public enum SingletonEnum {
INSTANCE;
private SingletonEnum() {
System.out.println("SingletonEnum initialized");
}
}
public class Singleton {
public static final Singleton INSTANCE = new Singleton();
private Singleton() {
System.out.println("Singleton initialized");
}
}
demonstrating lazy initialization
accessing non-enum singleton
Singleton initialized
accessing the enum singleton
SingletonEnum initialized
q.e.d.
Since the laziness is already there in either case, there is no reason to use a nested type as in your serializable singleton example. You could still use the simpler form
public class SerializableSingleton implements Serializable {
public static final SerializableSingleton INSTANCE = new SerializableSingleton();
private static final long serialVersionUID = 1L;
private SerializableSingleton() {
System.out.println("SerializableSingleton initialized");
}
protected Object readResolve() {
return INSTANCE;
}
}
The difference to an enum
is that fields are indeed getting serialized, but there’s no point in doing so as after deserializing, the reconstructed object gets replaced with the current runtime’s singleton instance. That’s what the readResolve()
method is for.
This is a semantic problem, as there can be an arbitrary number of different serialized versions but only one actual object, as otherwise it wouldn’t be a singleton anymore.
Just for completeness,
public class SerializableSingleton implements Serializable {
public static final SerializableSingleton INSTANCE = new SerializableSingleton();
private static final long serialVersionUID = 1L;
int value;
private SerializableSingleton() {
System.out.println("SerializableSingleton initialized");
}
public int getValue() {
return value;
}
public void setValue(int value) {
this.value = value;
}
protected Object readResolve() {
System.out.println("replacing "+this+" with "+INSTANCE);
return INSTANCE;
}
public String toString() {
return "SerializableSingleton{" + "value=" + value + '}';
}
}
SerializableSingleton single = SerializableSingleton.INSTANCE;
single.setValue(42);
byte[] data;
try(ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos)) {
oos.writeObject(single);
oos.flush();
data = baos.toByteArray();
}
single.setValue(100);
try(ByteArrayInputStream baos = new ByteArrayInputStream(data);
ObjectInputStream oos = new ObjectInputStream(baos)) {
Object deserialized = oos.readObject();
System.out.println(deserialized == single);
System.out.println(((SerializableSingleton)deserialized).getValue());
}
SerializableSingleton initialized
replacing SerializableSingleton{value=42} with SerializableSingleton{value=100}
true
100
So there’s no behavioral advantage in using an ordinary class here. Storing the fields contradicts the singleton nature and in the best case, these values have no effect and the deserialized object gets replaced by the actual runtime object, just like an enum
constant is deserialized to the canonical object in the first place.
Also, there is no difference regarding lazy initialization. So the non-enum class requires more code to write to get with nothing better.
The fact that the readResolve()
mechanism requires deserializing an object first, before it can be replaced by the actual result object is not only inefficient, it violates the singleton invariant temporarily and this violation is not always cleanly resolved at the end of the process.
This opens the possibility to the Serialization hack:
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.Serializable;
public class TestSer {
public static void main(String[] args) throws IOException, ClassNotFoundException {
SerializableSingleton singleton = SerializableSingleton.INSTANCE;
String data = "’\0\5sr\0\25SerializableSingleton\0\0\0\0\0\0\0\1\2\0\1L\0\1at\0\10"
+ "LSneaky;xpsr\0\6SneakyOÎæJ&r\234©\2\0\1L\0\1rt\0\27LSerializableSingleton;"
+ "xpq\0~\0\2";
try(ByteArrayInputStream baos=new ByteArrayInputStream(data.getBytes("iso-8859-1"));
ObjectInputStream oos = new ObjectInputStream(baos)) {
SerializableSingleton official = (SerializableSingleton)oos.readObject();
System.out.println(official+"\t"+(official == singleton));
Object inofficial = Sneaky.instance.r;
System.out.println(inofficial+"\t"+(inofficial == singleton));
}
}
}
class Sneaky implements Serializable {
static Sneaky instance;
SerializableSingleton r;
Sneaky(SerializableSingleton s) {
r = s;
}
private Object readResolve() {
return instance = this;
}
}
SerializableSingleton initialized
replacing SerializableSingleton@bebdb06 with SerializableSingleton@7a4f0f29
SerializableSingleton@7a4f0f29 true
SerializableSingleton@bebdb06 false
As demonstrated, the readObject()
returns the canonical instance as intended, but our Sneaky
class provides access to the second instance of the “singleton” which was supposed to be of a temporary nature.
The reason why this works, is precisely because fields are serialized and deserialized. The specially constructed (sneaky) stream data contains a field which actually doesn’t exist in the singleton, but since the serialVersionUID
matches, the ObjectInputStream
will accept the data, restore the object and then drop it because there’s no field to store it. But at this time, the Sneaky
instance got already hands on the singleton via a cyclic reference and remembers it.
The special treatment of enum
types make them immune against such attacks.