I was exploring enums in java to see how they could be abused and I came across a behaviour I couldn\'t explain. Consider the following class:
public class PROGR
This has to do with enums and class initialization.
First, enum
is just a fancy class
with constant fields. That is, the enum constants you declare are in reality just static fields. So
enum SomeEnum {
CONSTANT;
}
compiles to something similar to
final class SomeEnum extends Enum<SomeEnum> {
public static final SomeEnum CONSTANT = new SomeEnum();
}
Second, static fields are initialized in the left to right order they appear in the source code.
Next, execute either the class variable initializers and static initializers of the class, or the field initializers of the interface, in textual order, as though they were a single block.
In the following
final class SomeEnum extends Enum<SomeEnum> {
public static final SomeEnum CONSTANT = new SomeEnum();
public static final SomeEnum CONSTANT_2 = new SomeEnum();
}
CONSTANT
would be initialized first, and CONSTANT_2
second.
Third, an enum type will be [initialized][3] when you access one of its constants (which is really just a static field).
Fourth, if a class is currently being initialized by the current thread, you proceed normally.
If the
Class
object forC
indicates that initialization is in progress forC
by the current thread, then this must be a recursive request for initialization. ReleaseLC
and complete normally.
How does this all come together?
This
ENUM.ANIMALS.CATS.GARFIELD.RIVAL
is evaluated like
CATS cat = ENUM.ANIMALS.CATS.GARFIELD;
DOGS rvial = cat.RIVAL;
The first access to GARFIELD
forces the initialization of the enum
type CATS
. That begins initializing the enum constants in CATS
. Compiled, those appear like
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);
These get initialized in order. So FELIX
goes first. As part of its new instance creation expression, it accesses DOGS.AKAME
, where the type DOGS
is not yet initialized, so Java starts initializing it. The DOGS
enum type, compiled, looks like
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);
So we start with GIN
. In its new instance creation expression, it tries to access CATS.FELIX
. CATS
is current being initialized, so we just continue. CATS.FELIX
hasn't been assigned a value yet. It's currently in construction lower on the stack. So its value is null
. So GIN.RIVALS
gets a reference to null
. The same happens to all DOGS
' RIVAL
.
When all of the DOGS
are initialized, execution returns to
private static final CATS FELIX = new CATS(DOGS.AKAME);
where DOGS.AKAME
now refers to a fully initialize DOGS
object. That gets assigned to its CATS#RIVAL
field. Same for each of the CATS
. In other words, all the CATS
' RIVAL
field are assigned a DOGS
reference, but not the other way around.
Reordering the statements simply determines which enum
type gets initialized first.
When you call ENUM.ANIMALS.CATS.GARFIELD.RIVAL
, it will start by creating the CATS enum. When processing the first element, FELIX, it needs to create the DOGS enum so that DOGS.AKAME can be passed as a parameter to the CATS constructor.
The DOGS constructor receives a parameter of type CATS, but since CATS was not yet initialized all CATS.something will return null
, thus setting the RIVAL attribute to null
for all elements in the DOGS enum.
When all DOGS elements are created, it goes back to CATS and resumes the creation of its elements, passing the just created DOGS elements as parameters.
Similarly, when you invert the order of the calls it starts by creating the DOGS enum which causes the CATS elements RIVAL attribute to be set as null
.
If this is not clear, try to run your code with breakpoints set at the enum elements' declarations and at the constructors to understand it better.