¿Por qué usar cualquier función de lenguaje de programación? La razón por la que tenemos idiomas es para
- Los programadores para expresar de manera eficiente y correcta algoritmos en una forma que las computadoras pueden usar.
- Los encargados de comprender los algoritmos que otros han escrito y realizar los cambios correctamente .
Las enumeraciones mejoran tanto la probabilidad de corrección como la legibilidad sin escribir mucho repetitivo. Si está dispuesto a escribir repeticiones, puede "simular" enumeraciones:
public class Color {
private Color() {} // Prevent others from making colors.
public static final Color RED = new Color();
public static final Color AMBER = new Color();
public static final Color GREEN = new Color();
}
Ahora puedes escribir:
Color trafficLightColor = Color.RED;
La repetitiva anterior tiene el mismo efecto que
public enum Color { RED, AMBER, GREEN };
Ambos proporcionan el mismo nivel de comprobación de ayuda del compilador. Boilerplate es simplemente más tipeo. Pero ahorrar mucho tipeo hace que el programador sea más eficiente (ver 1), por lo que es una característica que vale la pena.
También vale la pena por al menos una razón más:
Cambiar declaraciones
Una cosa que la static final
simulación de enumeración anterior no le brinda son buenos switch
casos. Para los tipos de enumeración, el conmutador Java utiliza el tipo de su variable para inferir el alcance de los casos de enumeración, por lo que para lo enum Color
anterior simplemente necesita decir:
Color color = ... ;
switch (color) {
case RED:
...
break;
}
Tenga en cuenta que no está Color.RED
en los casos. Si no usa enum, la única forma de usar cantidades con nombre switch
es algo como:
public Class Color {
public static final int RED = 0;
public static final int AMBER = 1;
public static final int GREEN = 2;
}
Pero ahora una variable para contener un color debe tener tipo int
. El buen compilador comprobando la enumeración y elstatic final
simulación se ha ido. No feliz.
Un compromiso es usar un miembro de valor escalar en la simulación:
public class Color {
public static final int RED_TAG = 1;
public static final int AMBER_TAG = 2;
public static final int GREEN_TAG = 3;
public final int tag;
private Color(int tag) { this.tag = tag; }
public static final Color RED = new Color(RED_TAG);
public static final Color AMBER = new Color(AMBER_TAG);
public static final Color GREEN = new Color(GREEN_TAG);
}
Ahora:
Color color = ... ;
switch (color.tag) {
case Color.RED_TAG:
...
break;
}
Pero tenga en cuenta, ¡aún más repetitivo!
Usando una enumeración como un singleton
En la plantilla anterior puede ver por qué una enumeración proporciona una forma de implementar un singleton. En lugar de escribir:
public class SingletonClass {
public static final void INSTANCE = new SingletonClass();
private SingletonClass() {}
// all the methods and instance data for the class here
}
y luego accediendo con
SingletonClass.INSTANCE
solo podemos decir
public enum SingletonClass {
INSTANCE;
// all the methods and instance data for the class here
}
lo que nos da lo mismo. Podemos evitar esto porque las enumeraciones de Java se implementan como clases completas con solo un poco de azúcar sintáctica esparcida por la parte superior. Esto es de nuevo menos repetitivo, pero no es obvio a menos que el idioma te sea familiar. También me gusta el hecho de que se obtiene de las diversas funciones de enumeración a pesar de que no tienen mucho sentido para el singleton: ord
y values
, etc (De hecho, hay una simulación más complicado cuando Color extends Integer
que trabajará con el interruptor, pero es tan complicado que sea aún más muestra claramente por quéenum
es una mejor idea).
Hilo de seguridad
La seguridad de los hilos es un problema potencial solo cuando los singletons se crean perezosamente sin bloqueo.
public class SingletonClass {
private static SingletonClass INSTANCE;
private SingletonClass() {}
public SingletonClass getInstance() {
if (INSTANCE == null) INSTANCE = new SingletonClass();
return INSTANCE;
}
// all the methods and instance data for the class here
}
Si muchos hilos llaman getInstance
simultáneamente mientras INSTANCE
todavía es nulo, se puede crear cualquier número de instancias. Esto es malo. La única solución es agregar synchronized
acceso para proteger la variable INSTANCE
.
Sin embargo, el static final
código anterior no tiene este problema. Crea la instancia con entusiasmo en el tiempo de carga de la clase. La carga de clases está sincronizada.
El enum
singleton es efectivamente perezoso porque no se inicializa hasta el primer uso. La inicialización de Java también está sincronizada, por lo que varios subprocesos no pueden inicializar más de una instancia de INSTANCE
. Estás obteniendo un singleton perezosamente inicializado con muy poco código. Lo único negativo es la sintaxis bastante oscura. Necesitas conocer el idioma o comprender a fondo cómo funciona la carga de clases y la inicialización para saber qué está sucediendo.