Traditionally, a singleton is usually implemented as
public class Foo1
{
private static final Foo1 INSTANCE = new Foo1();
public static Foo1 getInstance(){ return INSTANCE; }
private Foo1(){}
public void doo(){ ... }
}
With Java's enum, we can implement a singleton as
public enum Foo2
{
INSTANCE;
public void doo(){ ... }
}
As awesome as the 2nd version is, are there any downsides to it?
(I gave it some thoughts and I'll answer my own question; hopefully you have better answers)
Best Answer
Some problems with enum singletons:
Committing to an implementation strategy
Typically, "singleton" refers to an implementation strategy, not an API specification. It is very rare for
Foo1.getInstance()
to publicly declare that it'll always return the same instance. If needed, the implementation ofFoo1.getInstance()
can evolve, for example, to return one instance per thread.With
Foo2.INSTANCE
we publicly declare that this instance is the instance, and there's no chance to change that. The implementation strategy of having a single instance is exposed and committed to.This problem is not crippling. For example,
Foo2.INSTANCE.doo()
can rely on a thread local helper object, to effectively have a per-thread instance.Extending Enum class
Foo2
extends a super classEnum<Foo2>
. We usually want to avoid super classes; especially in this case, the super class forced onFoo2
has nothing to do with whatFoo2
is supposed to be. That is a pollution to the type hierarchy of our application. If we really want a super class, usually it's an application class, but we can't,Foo2
's super class is fixed.Foo2
inherits some funny instance methods likename(), cardinal(), compareTo(Foo2)
, which are just confusing toFoo2
's users.Foo2
can't have its ownname()
method even if that method is desirable inFoo2
's interface.Foo2
also contains some funny static methodswhich appears to be nonsensical to users. A singleton usually shouldn't have pulbic static methods anyway (other than the
getInstance()
)Serializability
It is very common for singletons to be stateful. These singletons generally should not be be serializable. I can't think of any realistic example where it makes sense to transport a stateful singleton from one VM to another VM; a singleton means "unique within a VM", not "unique in the universe".
If serialization really does make sense for a stateful singleton, the singleton should explicitly and precisely specify what does it means to deserialize a singleton in another VM where a singleton of the same type may already exist.
Foo2
automatically commits to a simplistic serialization/deserialization strategy. That is just an accident waiting to happen. If we have a tree of data conceptually referencing a state variable ofFoo2
in VM1 at t1, through serialization/deserialization the value becomes a different value - the value of the same variable ofFoo2
in VM2 at t2, creating a hard to detect bug. This bug won't happen to the unserializableFoo1
silently.Restrictions of coding
There are things that can be done in normal classes, but forbidden in
enum
classes. For example, accessing a static field in the constructor. The programmer has to be more careful since he's working in a special class.Conclusion
By piggybacking on enum, we save 2 lines of code; but the price is too high, we have to carry all the baggages and restrictions of enums, we inadvertently inherit "features" of enum that have unintended consequences. The only alleged advantage - automatic serializability - turns out to be a disadvantage.