values - recorrer enum java
Los atributos enum de Java devuelven nulo según el orden de acceso (2)
Cuando llame a ENUM.ANIMALS.CATS.GARFIELD.RIVAL
, comenzará creando la enumeración CATS. Al procesar el primer elemento, FELIX, necesita crear la enumeración DOGS para que DOGS.AKAME pueda pasarse como un parámetro al constructor CATS.
El constructor DOGS recibe un parámetro de tipo CATS, pero como CATS aún no se había inicializado, todos los CATS.something devolverán null
, estableciendo así el atributo RIVAL en null
para todos los elementos en la enumeración DOGS.
Cuando se crean todos los elementos de DOGS, vuelve a CATS y reanuda la creación de sus elementos, pasando los elementos DOGS recién creados como parámetros.
De manera similar, cuando inviertes el orden de las llamadas, comienza creando la enumeración DOGS que causa que el atributo RIVAL de los elementos CATS se establezca como null
.
Si esto no está claro, intente ejecutar su código con los puntos de interrupción establecidos en las declaraciones de los elementos enum y en los constructores para comprenderlo mejor.
Estaba explorando enums en Java para ver cómo podían ser abusados y encontré un comportamiento que no podía explicar. Considera la siguiente clase:
public class PROGRAM {
public enum ENUM {;
public enum ANIMALS {;
public enum CATS {
FELIX(DOGS.AKAME),
GARFIELD(DOGS.WEED),
BUBSY(DOGS.GIN);
CATS(DOGS dog) {this.RIVAL = dog;}
public DOGS RIVAL;
}
public enum DOGS {
GIN(CATS.FELIX), WEED(CATS.BUBSY), AKAME(CATS.GARFIELD);
DOGS(CATS cat) {this.RIVAL = cat;}
public CATS RIVAL;
}
}
}
public static void main(String[] args) {
System.out.println(ENUM.ANIMALS.CATS.GARFIELD.RIVAL);
System.out.println(ENUM.ANIMALS.DOGS.GIN.RIVAL);
}
}
La primera instrucción en la función principal imprimirá ''WEED'', como se esperaba. El segundo imprimirá ''nulo''. Sin embargo, si los cambias, es decir,
System.out.println(ENUM.ANIMALS.DOGS.GIN.RIVAL);
System.out.println(ENUM.ANIMALS.CATS.GARFIELD.RIVAL);
la primera instrucción imprimirá ''FELIX'' y la segunda instrucción ahora imprimirá ''nulo''. ¿Hay alguien que pueda explicar este fenómeno?
Como referencia, estoy ejecutando Java (TM) SE Runtime Environment (compilación 1.8.0_05-b13)
Esto tiene que ver con las enumeraciones y la inicialización de clase.
Primero, enum
es solo una class
elegante con campos constantes. Es decir, las constantes de enum que declaras son en realidad solo campos static
. Asi que
enum SomeEnum {
CONSTANT;
}
compila a algo similar a
final class SomeEnum extends Enum<SomeEnum> {
public static final SomeEnum CONSTANT = new SomeEnum();
}
En segundo lugar, static
campos static
se inicializan en el orden de izquierda a derecha que aparecen en el código fuente.
A continuación, ejecute los inicializadores de variables de clase y los inicializadores estáticos de la clase, o los inicializadores de campo de la interfaz, en orden textual, como si fueran un solo bloque.
En el siguiente
final class SomeEnum extends Enum<SomeEnum> {
public static final SomeEnum CONSTANT = new SomeEnum();
public static final SomeEnum CONSTANT_2 = new SomeEnum();
}
CONSTANT
se inicializará primero y CONSTANT_2
segundo lugar.
En tercer lugar, un tipo enum
se [inicializará] [3] cuando acceda a una de sus constantes (que en realidad es solo un campo static
) .
En cuarto lugar, si una clase está siendo inicializada por el hilo actual, procede normalmente.
Si el objeto de
Class
paraC
indica que la inicialización está en curso paraC
por el hilo actual, entonces esta debe ser una solicitud recursiva para la inicialización. LiberaLC
y completa normalmente.
¿Cómo se une todo esto?
Esta
ENUM.ANIMALS.CATS.GARFIELD.RIVAL
es evaluado como
CATS cat = ENUM.ANIMALS.CATS.GARFIELD;
DOGS rvial = cat.RIVAL;
El primer acceso a GARFIELD
fuerza la inicialización del tipo enum
CATS
. Eso comienza a inicializar las constantes enum en CATS
. Compilado, esos aparecen como
private static final CATS FELIX = new CATS(DOGS.AKAME);
private static final CATS GARFIELD = new CATS(DOGS.WEED);
private static final CATS BUBSY = new CATS(DOGS.GIN);
Estos se inicializan en orden. Entonces FELIX
va primero. Como parte de su nueva expresión de creación de instancia, accede a DOGS.AKAME
, donde el tipo DOGS
aún no se ha inicializado, por lo que Java comienza a inicializarlo. El tipo de enumeración DOGS
, compilado, se parece a
private static final DOGS GIN = new DOGS(CATS.FELIX);
private static final DOGS WEED = new DOGS(CATS.BUBSY);
private static final DOGS AKAME = new DOGS(CATS.GARFIELD);
Entonces comenzamos con GIN
. En su nueva expresión de creación de instancia, intenta acceder a CATS.FELIX
. CATS
está siendo inicializado, así que simplemente continuamos. CATS.FELIX
todavía no se le ha asignado un valor. Actualmente está en construcción más abajo en la pila. Entonces su valor es null
. Entonces GIN.RIVALS
obtiene una referencia a null
. Lo mismo ocurre con el RIVAL
DOGS
.
Cuando todos los DOGS
se inicializan, la ejecución vuelve a
private static final CATS FELIX = new CATS(DOGS.AKAME);
donde DOGS.AKAME
ahora se refiere a un objeto DOGS
completamente inicializado. Eso se asigna a su campo CATS#RIVAL
. Lo mismo para cada uno de los CATS
. En otras palabras, a todos los CATS
'' RIVAL
field se les asigna una referencia de DOGS
, pero no al revés.
Reordenar las instrucciones simplemente determina qué tipo de enum
se inicializa primero.