Why use enumerations to implement the singleton pattern

Neither slacker, hunky-dory, nor volatile double-locking, nor even singletons using inner classes guarantee strict singletons under the clutches of reflection apis.

What problem does the traditional singleton approach solve

First, in most cases (not including interviews), traditional singletons are perfectly adequate.

The synchronized keyword is used to solve the simultaneous use of multithreading.

    public synchronized static SingleClassV1 getInstance(a){
        if(instance == null){
            instance = new SingleClassV1();
        }
        return instance;
    }
Copy the code

Consider that every time you get a singleton, you need to lock it and unlock it. Another invention was the double-lock check + volatile keyword:

    private static volatile SingleClassV2 instance;
    public static SingletonV2 getInstance(a) {
         if(instance == null) {synchronized (SingletonV2.class){
                 if(instance == null){
                     instance = newSingletonV2(); }}}return instance;
     }
Copy the code

Another way to solve the singleton is repeated initialization: using the class will only be initialized once, and someone invented a singleton inner writing method.

     private static class SingletonHolder {
         private static final SingletonV3 INSTANCE = new SingletonV3();
     }
     public static final SingletonV3 getInstance(a) {
         return SingletonHolder.INSTANCE;
     }
Copy the code

There are still problems

All of the above private constructors are nothing compared to reflection, thanks to the perversion of the Reflection API in Java.

Class<? > clazzV2 = Class.forName(SingleClassV2.class.getName()); Constructor<? > constructor = clazzV2.getDeclaredConstructors()[0];
    constructor.setAccessible(true);
    Object o = constructor.newInstance();
Copy the code

It seems that the private method is to prevent the gentleman not the villain

Why isn’t enumeration a problem

Let’s first look at what an enumeration-based singleton looks like.

public enum SingleClassV4 {
    INSTANCE;
    public String doSomeThing(a){
        return "hello world"; }}Copy the code

Of course, you wouldn’t know anything from the Java code.

Take a look at the bytecode using Javap.

public final class git.frank.SingleClassV4 extends java.lang.Enum<git.frank.SingleClassV4>
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC.ACC_FINAL.ACC_SUPER.ACC_ENUM
Copy the code

As you can see, enumeration types help us automatically inherit java.lang.Enum classes. In addition, the class is added with the ACC_ENUM flag in flags.

Then, look at the constructor of the enumerated class:

  privategit.frank.SingleClassV4(); descriptor: (Ljava/lang/String; I)V flags: ACC_PRIVATE Code: stack=3, locals=3, args_size=3
         0: aload_0
         1: aload_1
         2: iload_2
         3: invokespecial #6                  // Method java/lang/Enum."
      
       ":(Ljava/lang/String; I)V
      
         6: return
      LineNumberTable:
        line 3: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       7     0  this   Lgit/frank/SingleClassV4;
    Signature: #29                          // ()V
Copy the code

Enumerated classes also have constructors, which are no different from normal classes and can also be obtained by reflection:

Next, let’s see what happens to invoke by reflecting its constructor:

    constructor.newInstance();
Copy the code

The results are as follows:

Exception in thread "main" java.lang.IllegalArgumentException: Cannot reflectively create enum objects
	at java.lang.reflect.Constructor.newInstance(Constructor.java: 417).Copy the code

It’s easy to see why by looking at the newInstance method code:

    public T newInstance(Object ... initargs)
        throws InstantiationException, IllegalAccessException,
               IllegalArgumentException, InvocationTargetException
    {...if((clazz.getModifiers() & Modifier.ENUM) ! =0)
            throw new IllegalArgumentException("Cannot reflectively create enum objects"); . T inst = (T) ca.newInstance(initargs);return inst;
    }
Copy the code

Java’s reflection API determines if the current class is of an enumerated type when creating an object instance, otherwise it throws an exception.

conclusion

In traditional singletons, since private constructors do not completely prevent the creation of instances from outside, the implementation of those singletons is technically flawed.

The use of enumerations to create singletons is also explicitly stated in Effective Java.

Since Java’s reflection API already restricts the creation of instances of enumerated types by write-dead methods… Let’s get it over with.

Whoops, this is the same thing that was asked in the interview, who in a normal person would use reflection as a plug-in to break through singleton.