2

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;
    }
}
Jim C
  • 3,957
  • 25
  • 85
  • 162
  • 2
    "the main advantage of using enum for singleton was protecting from reflection risks" what would it mean if you deserialized a singleton twice, though? How could you be stopped from doing that, and ending up with multiple instances? – Andy Turner Feb 27 '20 at 15:54
  • as far as I understand, enum in scenario of serialization is worthless (I wrote "... when serializing an enum, field variables are not getting serialized..." above). Assuming I am right, your question doesn't apply here since it is not a PRO of enum – Jim C Feb 27 '20 at 16:31
  • 2
    my point is that mutable singletons are incompatible with serialization because there is nothing sensible you can do reconcile differences between the current state of the singleton and the state you load when deserializing. Either you stomp the current state (surprising for people not expecting it to change), drop the deserialized state (why bother serializing the state then), or create another instance (and it's not a singleton). – Andy Turner Feb 27 '20 at 16:46
  • 2
    First, `enum` types are as lazy as the other singleton examples. There is no difference. Further, your example of a serializable singleton is pointless. It will deserialize the stored values, then replace the deserialized object by the already existing, just like the `enum` would do in the first place. Except that the replacement is fragile as temporarily, two objects of your “singleton” exist. – Holger Feb 27 '20 at 16:57
  • @Holger I added "evidence" that by using "readResolve() method returning getInstance" I am really getting the original state. Doesn't it mean I had succesfully serialize/deserialize? Additionally, I didn't get your point while saying "two objects of my singleton exist" – Jim C Feb 27 '20 at 18:57
  • 2
    By the time, `readResolve()` is invoked, you have two instances of `DemoSingleton`, the one on which `readResolve()` is invoked and the one stored in `DemoSingletonHolder.INSTANCE`. This alone is a contradiction of the singleton invariant. And as @AndyTurner said, regardless of whose object’s state you’ll use, you will break someone’s expectations. Not to speak of what will happen if two threads deserialize two different versions of the `DemoSingleton` state at the same time. – Holger Feb 27 '20 at 19:23

1 Answers1

4

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

Also on Ideone

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.

Holger
  • 285,553
  • 42
  • 434
  • 765
  • Excellent explanation! I hope one day I reach this level of deep understanding. Originally my doubt was regard "issues with reflectation fixed by enum implementation no more necessary with Java modularity encapsulation". I have to be honest that based on what I learn in last hour I consider Singleton enum approach even now better than others approach. Thanks – Jim C Feb 27 '20 at 19:32