Search code examples
javastatic-initializationinitialization-order

Why do I get an NPE when a nested Enum references a parent static member in its constructor?


Conditions to recreate (as far as I can tell):

  1. nested enum references a parent static member
  2. nested class
  3. static member of parent class takes enum as an constructor argument to nested class
  4. 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());
  }
}

Solution

  • Here is what is happening:

    1. The main method starts executing
    2. You refer to Recreate.Car.TESLA
    3. The classloader starts to load and initialize enum Car. As noted below, class Recreate is NOT yet loaded or initialized.
    4. The initializer for TESLA refers to FEATURES
    5. This causes class Recreate to be loaded and initialized
    6. As part of static initialization of Recreate, Class Garage is loaded, intialized, and the instance ONE_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.