14

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)

irreputable
  • 657
  • 5
  • 7

3 Answers3

32

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 of Foo1.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 class Enum<Foo2>. We usually want to avoid super classes; especially in this case, the super class forced on Foo2 has nothing to do with what Foo2 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 like name(), cardinal(), compareTo(Foo2), which are just confusing to Foo2's users. Foo2 can't have its own name() method even if that method is desirable in Foo2's interface.

Foo2 also contains some funny static methods

    public static Foo2[] values() { ... }
    public static Foo2 valueOf(String name) { ... }
    public static <T extends Enum<T>> T valueOf(Class<T> enumType, String name)

which 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 of Foo2 in VM1 at t1, through serialization/deserialization the value becomes a different value - the value of the same variable of Foo2 in VM2 at t2, creating a hard to detect bug. This bug won't happen to the unserializable Foo1 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.

irreputable
  • 657
  • 5
  • 7
  • 2
    -1: Your discussion of serialization is wrong. The mechanism is not simplistic as enums are treated very differently from regular instances during deserialization. The problem described does **not** occur as the actual deserialization mechanism does not modify a "state variable". – scarfridge Dec 14 '12 at 15:15
  • see this example of confusion: http://www.coderanch.com/t/498782/java/java/Serialize-Deserialize-Java-enum – irreputable Dec 14 '12 at 15:24
  • if an enum if stateful, serialization will lose state, deserialization will gain spurious state. – irreputable Dec 14 '12 at 15:25
  • 3
    Actually the linked discussion stresses my point. Let me explain what I understood as the problem you claim to exist. Some object A references a second object B. The singleton instance S references B as well. Now we deserialize a previously serialized instance of the enum-based singleton (which, at the time of serialisation, referenced B' != B). What actually happens is, that A **and** S reference B, because B' will not be serialized. I thought you wanted to express that A and S do not reference the same object anymore. – scarfridge Dec 14 '12 at 16:08
  • 1
    Maybe we are not really talking about the same problem? – scarfridge Dec 14 '12 at 16:10
  • Using an `enum` saves a lot more than two lines of code. As noted in Effective Java item 3, this approach "provides an ironclad guarantee against multiple instantiation, even in the face of sophisticated serialization or reflection attacks." – Kevin Krumwiede May 31 '15 at 17:54
  • @Kevin Krumwiede: actually, that “ironclad guarantee” turns out to be a simple check inside the Reflection API which can be circumvented by other reflection attacks. I just verified that, using the right code I could sneakily create an additional `enum` instance with just four lines of code. But anyway, what’s the point of that guaranty, if it existed? You can break *any* code using Reflection, so why should the singleton property of a class be excluded? In most cases, creating a second instance of such a class is the most harmless thing an attacker could do. – Holger Jul 17 '15 at 07:56
  • @Holger I'd like to see that. And I'm sure Josh Bloch would, too. – Kevin Krumwiede Jul 17 '15 at 18:36
  • 1
    @Kevin Krumwiede: `Constructor> c=EnumType.class.getDeclaredConstructors()[0]; c.setAccessible(true); EnumType f=(EnumType)MethodHandles.lookup().unreflectConstructor(c).invokeExact("BAR", 1);`, e.g. a really nice example would be: `Constructor> c=Thread.State.class.getDeclaredConstructors()[0]; c.setAccessible(true); Thread.State f=(Thread.State)MethodHandles.lookup().unreflectConstructor(c).invokeExact("RUNNING_BACKWARD", -1);` ;^), tested under Java 7 and Java 8… – Holger Jul 17 '15 at 18:38
  • @Holger You should probably report this as a bug. – Kevin Krumwiede Jul 17 '15 at 19:10
  • @Kevin Krumwiede: the point is, that there is only one check within the Reflection code before the code goes into the internal implementation and there are other options of circumventing the check as long as you can use access override. As said, I don’t think that an “ironclad guaranty” is ever required, given *what* you can do when access override is not forbidden. So you have an ironclad but are sitting on a layer of ice… – Holger Jul 17 '15 at 19:17
  • you also check this article : http://javatutorialspot.com/java/java-enum-examples-advantages-disadvantages/ – Divyesh Kanzariya Aug 09 '17 at 06:13
  • enum route is not simply about saving lines. It also protects you from having multiple instances of your singleton in the JVM and thus violating the singleton responsibility. Upon deserialization, if readResolve() is not overridden then a second instantiation of the singleton can be created. Secondly, using reflection a user can change the private constructor accessor visibility to public and create more instances. You can read more here https://dzone.com/articles/java-singletons-using-enum – Metin Dagcilar Mar 15 '21 at 17:44
7

An enum instance is dependant on the class loader. ie if you have a second class loader that does not have the first class loader as a parent loading the same enum class you can get multiple instances in memory.


Code Sample

Create the following enum, and put its .class file into a jar by itself. ( of course the jar will have the correct package/folder structure )

package mad;
public enum Side {
  RIGHT, LEFT;
}

Now run this test, making sure that there is no copies of the above enum on the class path:

@Test
public void testEnums() throws Exception
{
    final ClassLoader root = MadTest.class.getClassLoader();

    final File jar = new File("path to jar"); // Edit path
    assertTrue(jar.exists());
    assertTrue(jar.isFile());

    final URL[] urls = new URL[] { jar.toURI().toURL() };
    final ClassLoader cl1 = new URLClassLoader(urls, root);
    final ClassLoader cl2 = new URLClassLoader(urls, root);

    final Class<?> sideClass1 = cl1.loadClass("mad.Side");
    final Class<?> sideClass2 = cl2.loadClass("mad.Side");

    assertNotSame(sideClass1, sideClass2);

    assertTrue(sideClass1.isEnum());
    assertTrue(sideClass2.isEnum());
    final Field f1 = sideClass1.getField("RIGHT");
    final Field f2 = sideClass2.getField("RIGHT");
    assertTrue(f1.isEnumConstant());
    assertTrue(f2.isEnumConstant());

    final Object right1 = f1.get(null);
    final Object right2 = f2.get(null);
    assertNotSame(right1, right2);
}

And we now have two objects representing the "same" enum value.

I agree that this is a rare and contrived corner case, and almost always an enum can be used for a java singleton. I do it myself. But the question asked about potential downsides and this note of caution is worth knowing about.

Mad G
  • 171
  • 1
  • 3
  • Can you find any references to that concern? –  Jul 10 '13 at 00:46
  • I have now edited and enhanced my initial answer with example code. Hopefully they will help illustrate the point and answer MichaelT's query as well. – Mad G Jul 11 '13 at 10:40
  • @MichaelT : I hope that answers your question :-) – Mad G Jul 15 '13 at 17:52
  • So, if the reason for use of enum (instead of class) for singleton was only its safety, there is not any reason for it now... Excellent, +1 – Gangnus Oct 02 '15 at 21:06
  • 1
    Will the "traditional" singleton implementation behave as expected even with two different class loaders? – Ron Klein Sep 14 '17 at 19:31
3

enum pattern can't be used for any Class that would throw an exception in the constructor. If this is required, use a factory:

class Elvis {
    private static Elvis self = null;
    private int age;

    private Elvis() throws Exception {
        ...
    }

    public synchronized Elvis getInstance() throws Exception {
        return self != null ? self : (self = new Elvis());
    }

    public int getAge() {
        return age;
    }        
}