Unable to construct EnumMap inside the initializer of the enum being mapped

651 views Asked by At

In my code, I have an enum in which each value stores a separate EnumMap. However, when I try to initialize the EnumMap in a constructor or initializer, using the following code:

public static void main(String[] args) {
   RPS.values(); // forces initialization of enum values
}

enum RPS {
   ROCK,
   PAPER,
   SCISSORS;
   EnumMap<RPS,Boolean> matchups;
   {
      matchups = new EnumMap<>(RPS.class);
   }
}

it throws an ExceptionInInitializerError caused by a NullPointerException. However, no error is thrown when I initialize it outside of the constructor, like in the following code:

public static void main(String[] args) {
   for (RPS val:RPS.values())
      val.matchups = new EnumMap<>(RPS.class);
}

enum RPS {
   ROCK,
   PAPER,
   SCISSORS;
   EnumMap<RPS,Boolean> matchups;
}

Why does this error occur and how can I fix it?

2

There are 2 answers

0
DuncG On BEST ANSWER

Just move your initialisation to a static block, as RPS enum values are determined by then:

enum RPS {
   ROCK,
   PAPER,
   SCISSORS;

   EnumMap<RPS,Boolean> matchups;
   static
   {
        for (RPS val:RPS.values())
            val.matchups = new EnumMap<>(RPS.class);
   }
}
0
Tashkhisi On

Simple answer:

First you should know something about enum in java, enum in java is an instance controlled class. This means that when you define an enum you are defining a class with fixed number of instances. these instances are created at runtime when the class of enum is loaded by classloader. so why your code throw NullPointerException? here when your ClassLoader load RPS it tries to create all fixed instance for this enum and as you know each instance has a field which is called matchups. so imagine it tries to create ROCK instance which is the first instance of class RPS, to create this instance it should call its constructor in which you initialize matchups. But how matchups (matchups here is an EnumMap) will be created here? The EnumMap implementation uses the values of already created and defined enum (which passed to it as first generic parameter), but here you are defining EnumMap based on its containing enum which is RPS, on the other hand instances of RPS can not be created until their matchups field created, you can clearly see that there is a loop here. and you can't use enum in this recursive way. In your second implementation you do the correct thing here you delay the creation of matchups and initialized this filed after RPS enum is created and ready so this way construction of matchups will be successful.

More professional answer:

EnumMap implementation internally uses array to implement this structure. and on construction time it creates an array of size of elements that are exist in enum passed to it. Inside EnumMap implementation there is a method called getKeyUniverse(Class keyType) which Returns all of the values comprising K, note that K here is the enum that you have passed as first argument to EnumMap (here K is RPS), getKeyUniverse(Class keyType) method is called in constructor when you want to create new instance with new operator, since RPS is not completely created at this time this method can not return all of the values comprising RPS so return null. and calling lentch on this array (which is null) throw NullPointerException. Throwing this exception is clearly defined in method documentation.