Conditions to recreate (as far as I can tell):
- nested enum references a parent static member
- nested class
- static member of parent class takes enum as an constructor argument to nested class
- enum is referenced by an external class before anything else in the parent class
Run this code online: https://repl.it/repls/PlushWorthlessNetworking
import java.util.ArrayList;
class Recreate {
private static ArrayList FEATURES = new ArrayList();
public enum Car {
TESLA(FEATURES);
Car(ArrayList l) { }
}
public static class Garage {
final Car car;
Garage(Car car) {
this.car = car;
}
}
public static Garage ONE_CAR_GARAGE = new Garage(Car.TESLA);
}
class Main {
public static void main(String[] args) {
// inclusion of this line causes the next line to NPE
System.out.println(Recreate.Car.TESLA);
System.out.println(Recreate.ONE_CAR_GARAGE.car.toString());
}
}
Here is what is happening:
- The main method starts executing
- You refer to
Recreate.Car.TESLA
- The classloader starts to load and initialize
enum Car
. As noted below, classRecreate
is NOT yet loaded or initialized. - The initializer for
TESLA
refers toFEATURES
- This causes class
Recreate
to be loaded and initialized - As part of static initialization of
Recreate
, ClassGarage
is loaded, intialized, and the instanceONE_CAR_GARAGE
is created.
The problem here is that at this point, the construction of enum Car
is not complete, and Car.TESLA
has the value null
.
Even though classes may be nested, it is not the case that nested classes are loaded and initialized as part of the outer class initialization. They may look nested in the source but each and every class is independent. Static nested classes are equivalent to top-level classes. Non-static classes are also the same but have the ability to refer to members in the containing class via a hidden reference.
You can see for yourself if you run this in a debugger, put breakpoints in several places, and examine the stack at each breakpoint.
I tested/debugged this in Eclipse with the following code, with breakpoints set where indicated. It's slightly different from your code but shouldn't behave differently:
public class Foo5
{
static class Recreate {
private static ArrayList FEATURES = new ArrayList();
public enum Car {
TESLA(FEATURES);
Car(ArrayList l) {
System.out.println("car"); // *** Breakpoint ***
}
}
public static Garage ONE_CAR_GARAGE = new Garage(Car.TESLA);
public static class Garage {
final Car car;
Garage(Car car) {
this.car = car; // *** Breakpoint ***
}
}
}
public static void main(String[] args) throws Exception {
Recreate.Car car = Recreate.Car.TESLA;
System.out.println(Recreate.Car.TESLA);
System.out.println(Recreate.ONE_CAR_GARAGE.car.toString());
}
}
The first breakpoint you will hit will be the one in the Garage(Car car)
constructor. Examining the stack at that point you will see
Foo5$Recreate$Garage.<init>(Foo5$Recreate$Car) line: 23
Foo5$Recreate.<clinit>() line: 17
Foo5$Recreate$Car.<clinit>() line: 12
Foo5.main(String[]) line: 29
So when the Garage
constructor is called, it has not yet returned from creating Car
. This is dictated by the convoluted dependencies you have created between classes, so the solution is to untangle the dependencies. How you do that will depend on your ultimate goals.