【问题标题】:Creating a final Java class array of enum constants with values( )用 values() 创建一个最终的 Java 类枚举常量数组
【发布时间】:2012-09-16 14:09:10
【问题描述】:

在 Java 枚举类中,我想创建一个 final static 数组,其中包含该类的 values()。当我按照以下几行执行此操作时,结果数组为null

public enum Name {
    E1( stuff ), E2( stuff );
    private static final Name[] values = Name.values();

    private Name( stuff ) { more stuff; }
}

我也尝试通过调用显式类设置器方法来执行此操作,但这给出了java.lang.ExceptionInInitializerError 异常。

我知道问题是由一些浅层依赖引起的,因为前面代码中的stuff 使用了其他类,这些类本身依赖于枚举类。

是否有经过测试和验证的技术可以满足我的需求?

【问题讨论】:

  • 这就是你所有的枚举定义吗?它没有采用stuff 的构造函数吗?如果可能的话,你能粘贴异常堆栈跟踪吗?
  • 为什么需要一个变量?您不能在使用此变量的任何地方调用 Name.values 吗?
  • @JohnWatts Name.values( ) 返回一个防御性副本。我想避免创建副本的开销。
  • @JohnWatts 附议 - 这看起来很像过早优化。
  • @Vikdor 我已经添加了“构造函数”。

标签: java class constructor enums


【解决方案1】:

tl;dr: 您尝试做的事情是不可能的 - 枚举类型的静态字段在所有构造函数调用完成之前不会被初始化。


考虑这个例子:

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);
  }
}

请注意,dump 方法存在的原因是尝试从Name 的构造函数内部引用value 字段是编译时错误(Java Language Spec section 8.9.2)。使用此测试工具:

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

我们得到

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

使用javap 反编译Name 类,我们看到以下内容:

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

编译器创建一个私有字段$VALUES保存值数组,values()方法实现为{ return (Name[])$VALUES.clone() }。那么$VALUES是如何初始化的呢?

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 "<init>":(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 "<init>":(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

}

我们在这里看到的是初始化本质上是这样的:

// 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();

所以在执行构造函数调用期间,values 字段将为 null,values() 方法将抛出 NullPointerException(将被包装在 ExceptionInInitializerError 中)。

【讨论】:

  • 感谢您的解释。我仍然不确定它是否解释了一切,因为我没有在枚举成员的构造函数中引用数组。错误发生在我的应用程序执行的很晚。
  • 你说“前面代码中的东西使用了其他类,这些类本身依赖于枚举类”,这听起来像是你从Name构造函数中调用了一些东西,而后者又在调用回到枚举。基本上答案是,如果 Name 构造函数位于当前调用堆栈上的任何位置,您将始终为 values 字段获得 null
  • 没有。不是这种情况。当我知道导致问题的原因后,我会回来解释。
  • 你好。结果发现对枚举构造函数的“递归”调用。我已经消除了这个问题,现在(惊喜,惊喜)使用类数组现在也可以工作了。我应该已经发现了这个问题......
【解决方案2】:

您能否提供一个发生这种情况的示例,因为它不应该为空。

public class Main {
    public enum Name {
        E1(  ), E2(  );
        private static final Name[] VALUES = Name.values();
    }


    public static void main(String... args) {
        System.out.println(Name.VALUES);
        System.out.println(Arrays.asList(Name.VALUES));
    }
}

打印

[LMain$Name;@717e5fde
[E1, E2]

【讨论】:

  • 在我的情况下 values( ) 确实返回 null。如果我按照以下行在枚举类的类方法中添加断言,则会收到断言错误:assert( values != null ).
  • 你能提供一个独立的例子吗?你能运行我给出的例子来看看它是否发生吗?顺便说一句,您使用的是哪个版本的 Java?
  • 您的示例有效(当然)。正如我在问题中解释的那样,由于某些类依赖关系,我的代码中存在一些复杂性。我无法创建一个最小的示例。
猜你喜欢
  • 1970-01-01
  • 2015-08-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多