Creating a final Java class array of enum constants with values( )

后端 未结 2 896
故里飘歌
故里飘歌 2021-01-21 07:54

Inside a Java enumerated class, I\'d like to create a final static array containing the values() of the class. When I do this along the following lines

2条回答
  •  抹茶落季
    2021-01-21 07:58

    tl;dr: what you're trying to do isn't possible - static fields of an enum type don't get initialized until after all the constructor calls have completed.


    Consider this example:

    public enum Name {
      E1("hello"), E2("world");
    
      private static final Name[] values = values();
    
      private Name(String val) {
        System.out.println("val = " + val);
        dump();
      }
    
      protected void dump() {
        System.out.println("this = " + this + ", values = " + values);
      }
    }
    

    Note that the reason for the existence of the dump method is that it is a compile-time error (Java Language Spec section 8.9.2) to try and reference the value field from inside the constructor of Name. With this test harness:

    public class Main {
      public static void main(String... args) throws Exception {
        System.out.println(Name.values());
      }
    }
    

    we get

    $ java Main
    val = hello
    this = E1, values = null
    val = world
    this = E2, values = null
    [LName;@35960f05
    

    Decompiling the Name class with javap we see the following:

    private static final Name[] $VALUES;
    
    public static Name[] values();
      Code:
       0:   getstatic   #1; //Field $VALUES:[LName;
       3:   invokevirtual   #2; //Method "[LName;".clone:()Ljava/lang/Object;
       6:   checkcast   #3; //class "[LName;"
       9:   areturn
    

    The compiler creates a private field $VALUES holding the value array, and the values() method is implemented as { return (Name[])$VALUES.clone() }. So how does $VALUES get initialized?

    static {};
      Code:
       0:   new #4; //class Name
       3:   dup
       4:   ldc #19; //String E1
       6:   iconst_0
       7:   ldc #20; //String hello
       9:   invokespecial   #21; //Method "":(Ljava/lang/String;ILjava/lang/String;)V
       12:  putstatic   #22; //Field E1:LName;
       15:  new #4; //class Name
       18:  dup
       19:  ldc #23; //String E2
       21:  iconst_1
       22:  ldc #24; //String world
       24:  invokespecial   #21; //Method "":(Ljava/lang/String;ILjava/lang/String;)V
       27:  putstatic   #25; //Field E2:LName;
       30:  iconst_2
       31:  anewarray   #4; //class Name
       34:  dup
       35:  iconst_0
       36:  getstatic   #22; //Field E1:LName;
       39:  aastore
       40:  dup
       41:  iconst_1
       42:  getstatic   #25; //Field E2:LName;
       45:  aastore
       46:  putstatic   #1; //Field $VALUES:[LName;
       49:  invokestatic    #26; //Method values:()[LName;
       52:  putstatic   #18; //Field values:[LName;
       55:  return
    
    }
    

    What we see here is that the initialization essentially does:

    // compiler-generated initialization code
    E1 = new Name("hello");
    E2 = new Name("world");
    $VALUES = new Name[] {E1, E2};
    
    // static initializer of the values field
    values = Name.values();
    

    so during the execution of the constructor calls, the values field will be null and the values() method will throw a NullPointerException (which will get wrapped in an ExceptionInInitializerError).

提交回复
热议问题