Uploaded image for project: 'JDK'
  1. JDK
  2. JDK-7176515

ExceptionInInitializerError for an enum with multiple switch statements

XMLWordPrintable

    • Icon: Bug Bug
    • Resolution: Fixed
    • Icon: P4 P4
    • 21
    • 7
    • tools
    • b16
    • x86
    • linux

      FULL PRODUCT VERSION :
      java version "1.7.0_04"
      Java(TM) SE Runtime Environment (build 1.7.0_04-b20)
      Java HotSpot(TM) 64-Bit Server VM (build 23.0-b21, mixed mode)

      java version "1.6.0_31"
      Java(TM) SE Runtime Environment (build 1.6.0_31-b04-415-10M3646)
      Java HotSpot(TM) 64-Bit Server VM (build 20.6-b01-415, mixed mode)


      ADDITIONAL OS VERSION INFORMATION :
      Linux hostname 2.6.18-274.18.1.el5 #1 SMP Thu Feb 9 12:45:44 EST 2012 x86_64 x86_64 x86_64 GNU/Linux

      Darwin machinename.local 10.8.0 Darwin Kernel Version 10.8.0: Tue Jun 7 16:33:36 PDT 2011; root:xnu-1504.15.3~1/RELEASE_I386 i386


      A DESCRIPTION OF THE PROBLEM :
      Consider an enum class A which has a switch statement on another enum type B in the constructor, and which has a method (not invoked by the constructor, of course!) with a switch (this). The enum class A ends up compiled in a way that guarantees an ExceptionInInitializerError caused by a NullPointerException when the class is initialized.

      I gather that the presence of a switch ( enumInstance ) { ... } causes javac to construct a synthetic static inner class containing a constant int[] switch map for each switch on an enum. The problem is that all switch maps for the given class A are placed in the same inner class, and all the int[]'s are initialized by the inner class' static constructor. The presence of the switch on an instance of B in the constructor for an instance of A thus causes A's inner class to be initialized during initialization of the enum class A. That causes all the int[]'s to be initialized including the one for the switch (this). That, in turn, causes the not-yet initialized fields of A to be read, resulting in the NullPointerException which gets wrapped in ExceptionInInitializerError.

      STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
      compile and run the attached Hello World program

      EXPECTED VERSUS ACTUAL BEHAVIOR :
      EXPECTED -
      Hello World!
      ACTUAL -
      Exception in thread "main" java.lang.ExceptionInInitializerError
      at MyEnum.<init>(MyEnum.java:22)
      at MyEnum.<clinit>(MyEnum.java:5)
      Caused by: java.lang.NullPointerException
      at MyEnum.values(MyEnum.java:3)
      at MyEnum$1.<clinit>(MyEnum.java:38)
      ... 2 more


      ERROR MESSAGES/STACK TRACES THAT OCCUR :
      Exception in thread "main" java.lang.ExceptionInInitializerError
      at MyEnum.<init>(MyEnum.java:22)
      at MyEnum.<clinit>(MyEnum.java:5)
      Caused by: java.lang.NullPointerException
      at MyEnum.values(MyEnum.java:3)
      at MyEnum$1.<clinit>(MyEnum.java:38)
      ... 2 more


      REPRODUCIBILITY :
      This bug can be reproduced always.

      ---------- BEGIN SOURCE ----------
      import java.math.RoundingMode;

      public enum MyEnum {

          FIRST(RoundingMode.CEILING),
          
          SECOND(RoundingMode.HALF_DOWN),
          
          THIRD(RoundingMode.UNNECESSARY),
          
          FOURTH(RoundingMode.HALF_EVEN),
          
          FIFTH(RoundingMode.HALF_DOWN),
          
          SIXTH(RoundingMode.CEILING),
          
          SEVENTH(RoundingMode.UNNECESSARY);
          
          private final RoundingMode mode;
          
          private MyEnum(RoundingMode mode) {
              switch ( mode ) {
              case CEILING:
              case HALF_DOWN:
              case UNNECESSARY:
              case HALF_EVEN:
                  break;
              default:
                  throw new IllegalArgumentException(mode + " is not a valid RoundingMode for MyEnum");
              }
              this.mode = mode;
          }
          
          /**
           * Dead code.
           */
          public boolean isOdd() {
              switch ( this ) {
              case FIRST:
              case THIRD:
              case FIFTH:
              case SEVENTH:
                  return true;
              default:
                  return false;
              }
          }
          
          public static void main(String[] args) {
              System.out.println("Hello World!");
          }
          
      }

      ---------- END SOURCE ----------

      CUSTOMER SUBMITTED WORKAROUND :
      Workarounds include:

      * Use the Eclipse compiler instead of javac. It seems that Eclipse does not synthesize an inner class for the switch maps; instead it puts lazily constructed switch tables inside the class containing the switch. Thus, the switch table for the switch(this) is not constructed until it is needed and, at least for a correctly written program, that won't be until after the enum class has been initialized. Indeed, the attached program prints "Hello World!" if compiled using Eclipse.

      * Don't write methods that switch(this). Use a different idiom instead, such as a private field to hold the isOdd state, or overridden enum methods like

      FIRST(RoundingMode.CEILING) {
        @Override public boolean isOdd() { return true; }
      }

            vromero Vicente Arturo Romero Zaldivar
            webbuggrp Webbug Group
            Votes:
            0 Vote for this issue
            Watchers:
            6 Start watching this issue

              Created:
              Updated:
              Resolved:
              Imported:
              Indexed: