Take the 2-minute tour ×
Programmers Stack Exchange is a question and answer site for professional programmers interested in conceptual questions about software development. It's 100% free, no registration required.

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)

share|improve this question
12  
The downside is that it is a singleton. A totally overrated (cough) "pattern" –  Thomas Eding Dec 14 '12 at 0:58
    
After Josh Bloch's book was published, someone found some problems with this solution. I cannot remember the details and the subsequent improved solution. Hopefully someone who does will chime in. –  Jubbat Dec 14 '12 at 2:58

3 Answers 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;
    }        
}
share|improve this answer

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.

share|improve this answer
    
Can you find any references to that concern? –  MichaelT Jul 10 '13 at 0: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
    
That it does, thank you. –  MichaelT Jul 15 '13 at 17:54

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.

share|improve this answer
1  
-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: coderanch.com/t/498782/java/java/… –  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
1  
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

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.