Implementación de Singleton con una enumeración (en Java)

178

He leído que es posible implementar Singletonen Java usando un Enumtal como:

public enum MySingleton {
     INSTANCE;   
}

Pero, ¿cómo funciona lo anterior? Específicamente, un Objecttiene que ser instanciado. Aquí, ¿cómo se MySingletonestá instanciando? ¿Quién está haciendo new MySingleton()?

Anand
fuente
24
¿Quién está haciendo nuevo MySingleton () El JVM es.
Sotirios Delimanolis
37
INSTANCEes igual que public static final MySingleton INSTANCE = new MySingleton();.
Pshemo
66
ENUM - Un singleton garantizado.
No es un error
Lea dzone.com/articles/java-singletons-using-enum , por qué usar enum y no los otros métodos. Corto: los problemas comienzan cuando se usa la serialización y la reflexión.
Miyagi

Respuestas:

203

Esta,

public enum MySingleton {
  INSTANCE;   
}

tiene un constructor vacío implícito. Hazlo explícito en su lugar,

public enum MySingleton {
    INSTANCE;
    private MySingleton() {
        System.out.println("Here");
    }
}

Si luego agregaste otra clase con un main()método como

public static void main(String[] args) {
    System.out.println(MySingleton.INSTANCE);
}

Tu verias

Here
INSTANCE

enumlos campos son constantes de tiempo de compilación, pero son instancias de su enumtipo. Y, se construyen cuando se hace referencia al tipo de enumeración por primera vez .

Elliott Frisch
fuente
13
Debe agregar que, de manera predeterminada, las enumeraciones tienen un constructor privado implícito y que no es necesario agregar explícitamente un constructor privado a menos que realmente tenga el código que necesita ejecutar en ese constructor
Nimrod Dayan
cada campo enum crea una instancia solo una vez, por lo que no es necesario crear un constructor privado.
Pravat Panda
2
enumeración pública MySingleton {INSTANCE, INSTANCE1; } y luego System.out.println (MySingleton.INSTANCE.hashCode ()); System.out.println (MySingleton.INSTANCE1.hashCode ()); Imprime diferentes códigos hash. ¿Significa que se crean dos objetos de MySingleton?
Scott millas
@scottmiles Sí. Porque tienes dos instancias. Un singleton, por definición, tiene uno.
Elliott Frisch
El privatemodificador no tiene significado para un enumconstructor y es completamente redundante.
Dmitri Nesteruk
76

Un enumtipo es un tipo especial de class.

Tu enumrealmente será compilado a algo así

public final class MySingleton {
    public final static MySingleton INSTANCE = new MySingleton();
    private MySingleton(){} 
}

Cuando su código accede por primera vez INSTANCE, la clase MySingletonserá cargada e inicializada por la JVM. Este proceso inicializa el staticcampo anterior una vez (perezosamente).

Sotirios Delimanolis
fuente
¿Esta clase también contiene constructor privado ()? Creo que sería
Yasir Shabbir Choudhary
public enum MySingleton { INSTANCE,INSTANCE1; }y luego System.out.println(MySingleton.INSTANCE.hashCode()); System.out.println(MySingleton.INSTANCE1.hashCode());imprime diferentes códigos hash. ¿Significa que se crean dos objetos de MySingleton?
Scott millas
2
@scottmiles Sí, son solo dos enumconstantes de diferencia .
Sotirios Delimanolis
2
@caf, claro. Ver aquí: docs.oracle.com/javase/specs/jls/se8/html/jls-8.html#jls-8.9.3
Sotirios Delimanolis
@SotiriosDelimanolis, ¿es seguro este enfoque con enum thread?
abg
63

En este libro de prácticas recomendadas de Java de Joshua Bloch, puede encontrar explicaciones de por qué debe aplicar la propiedad Singleton con un constructor privado o un tipo Enum. El capítulo es bastante largo, así que resumiéndolo:

Convertir una clase en Singleton puede dificultar la prueba de sus clientes, ya que es imposible sustituir una implementación simulada por un singleton a menos que implemente una interfaz que sirva como su tipo. El enfoque recomendado es implementar Singletons simplemente haciendo un tipo de enumeración con un elemento:

// Enum singleton - the preferred approach
public enum Elvis {
INSTANCE;
public void leaveTheBuilding() { ... }
}

Este enfoque es funcionalmente equivalente al enfoque de campo público, excepto que es más conciso, proporciona la maquinaria de serialización de forma gratuita y ofrece una garantía irrefrenable contra la creación de instancias múltiples, incluso frente a ataques sofisticados de serialización o reflexión.

Si bien este enfoque aún no se ha adoptado ampliamente, un tipo de enumeración de un solo elemento es la mejor manera de implementar un singleton.

ekostadinov
fuente
Creo que, para hacer que un singleton sea comprobable, podría usar un patrón de estrategia y hacer que todas las acciones del singleton pasen por la estrategia. Entonces puede tener una estrategia de "producción" y una estrategia de "prueba" ...
Erk
9

Como todas las instancias de enumeración, Java crea instancias de cada objeto cuando se carga la clase, con cierta garantía de que se instancia exactamente una vez por JVM . Piense en la INSTANCEdeclaración como un campo final estático público: Java creará una instancia del objeto la primera vez que se haga referencia a la clase.

Las instancias se crean durante la inicialización estática, que se define en la Especificación del lenguaje Java, sección 12.4 .

Por lo que vale, Joshua Bloch describe este patrón en detalle como elemento 3 de Effective Java Second Edition .

Jeff Bowman
fuente
6

Dado que Singleton Pattern se trata de tener un constructor privado y llamar a algún método para controlar las instancias (como algunos getInstance), en Enums ya tenemos un constructor privado implícito.

No sé exactamente cómo la JVM o algún contenedor controla las instancias de nuestro Enums, pero parece que ya usa un implícito Singleton Pattern, la diferencia es que no llamamos a getInstance, solo llamamos Enum.

Bruno Franco
fuente
3

Como se ha mencionado, hasta cierto punto, una enumeración es una clase de Java con la condición especial de que su definición debe comenzar con al menos una "constante de enumeración".

Aparte de eso, y que las enumeraciones no pueden extenderse o usarse para extender otras clases, una enumeración es una clase como cualquier clase y la usa agregando métodos debajo de las definiciones constantes:

public enum MySingleton {
    INSTANCE;

    public void doSomething() { ... }

    public synchronized String getSomething() { return something; }

    private String something;
}

Accede a los métodos de singleton a lo largo de estas líneas:

MySingleton.INSTANCE.doSomething();
String something = MySingleton.INSTANCE.getSomething();

El uso de una enumeración, en lugar de una clase, es, como se ha mencionado en otras respuestas, principalmente sobre una instanciación segura de subprocesos del singleton y una garantía de que siempre será solo una copia.

Y, quizás, lo más importante, que este comportamiento está garantizado por la propia JVM y la especificación Java.

Aquí hay una sección de la especificación de Java sobre cómo se evitan varias instancias de una instancia de enum:

Un tipo de enumeración no tiene instancias distintas de las definidas por sus constantes de enumeración. Es un error en tiempo de compilación intentar crear una instancia explícita de un tipo de enumeración. El método de clonación final en Enum asegura que las constantes enum nunca se puedan clonar, y el tratamiento especial por el mecanismo de serialización asegura que nunca se creen instancias duplicadas como resultado de la deserialización. Se prohíbe la creación de instancias reflexivas de tipos de enumeración. Juntas, estas cuatro cosas aseguran que no existan instancias de un tipo enum más allá de las definidas por las constantes enum.

Vale la pena señalar que después de la instanciación, cualquier problema de seguridad de subprocesos debe manejarse como en cualquier otra clase con la palabra clave sincronizada, etc.

Erk
fuente