为什么我不能从Java中的专用枚举值访问静态最终成员

Jan*_*net 15 java compiler-construction enums types

我想知道为什么,尽管在Java中执行以下操作是完全有效的

public enum Test {
   VALUE1() {
      public static final String CONST_RELATED_TO_VALUE1 = "constant";
      public static final String OTHER_CONST_RELATED_TO_VALUE1 = "constant";
   },
   VALUE2() {
      public static final String CONST_RELATED_TO_VALUE2 = "constant";
   },
   VALUE3;
}
Run Code Online (Sandbox Code Playgroud)

正如人们期望的那样访问常量Test.VALUE1.CONST_RELATED_TO_VALUE1不起作用.

现在我明白了VALUE1,VALUE2等等,实际上都普遍被视为类型的静态终审Test,因此不具备这些领域,但信息理论上应该可以在编译时,它可以很容易地验证运行一个小测试

     // print types and static members
     for (Object o: Test.values()) {
        System.out.println(o.toString() + ": " + o.getClass());
        for (Field field : o.getClass().getDeclaredFields()) {
            if (java.lang.reflect.Modifier.isStatic(field.getModifiers())) {
                System.out.println("\t" + field);
            }
        }             
     }
Run Code Online (Sandbox Code Playgroud)

这导致以下输出

VALUE1: class Test$1                                                                                                                                                                               
        public static final java.lang.String Test$1.CONST_RELATED_TO_VALUE1                                                                                                                
        public static final java.lang.String Test$1.OTHER_CONST_RELATED_TO_VALUE1                                                                                                          
VALUE2: class Test$2                                                                                                                                                                               
        public static final java.lang.String Test$2.CONST_RELATED_TO_VALUE2                                                                                                                
VALUE3: class Test                                                                                                                                                                                 
        public static final Test Test.VALUE1                                                                                                                                    
        public static final Test Test.VALUE2                                                                                                                                    
        public static final Test Test.VALUE3                                                                                                                                    
        private static final Test[] Test.$VALUES
Run Code Online (Sandbox Code Playgroud)

很显然,我们实际上在运行时有适当的专用子类VALUE1VALUE2.但它看起来也是我们丢失具体类型信息的原因VALUE1,VALUE2也是编译器为基本枚举类生成静态枚举值的方式,Test如下所示VALUE3:所有成员都是类型Test,具体类型被丢弃.

但是,在我看来,如果编译器只是保留这些类型

        public static final Test$1 Test.VALUE1                                                                                                                                    
        public static final Test$2 Test.VALUE2                                                                                                                                    
        public static final Test Test.VALUE3                                                                                                                                    
Run Code Online (Sandbox Code Playgroud)

所有周围的代码仍然有效.此外,我们还可以做什么,我最初尝试并获得CONST_RELATED_TO_VALUE1通过Test.VALUE1,这是现在显然类型的实例,Test$1而不是只Test,虽然它一般应避免,似乎在这种情况下,完全没有通过一个实例来访问静态成员.

现在正如许多人正确指出的那样,使用左侧的匿名类不是有效的Java代码,并且可能也不允许在没有一些主要规范更改的情况下编译器.但是,这可以通过使用命名的内部类来轻松解决,所以我们可以

        public static final Test.Value1 Test.VALUE1                                                                                                                                    
        public static final Test.Value2 Test.VALUE2                                                                                                                                    
        public static final Test Test.VALUE3                                                                                                                                    
Run Code Online (Sandbox Code Playgroud)

这甚至为调试提供了额外的好处,即内部子类名称清楚地映射到相应的枚举值.

现在我明白了必须进行一些细微的改动,但是从匿名到命名的类而不是丢弃这些类似乎是一个很小的改变,这看起来很不错,没有一个简单的方法来使用重写来模拟它成员或什么的.

所以我想知道为什么除了时间之外没有像这样实现?我是否遗漏了一些关键的东西,这会阻止编译器这样做(关于实现复杂性或类型系统不可能)是否只是为了保持简单而没有实现,因为这些行没有时间或东西?

(我主要是寻找为什么决定从编译器/类型系统的角度来实现它的原因,而不是实际的替代方案,因为肯定有一对,尽管它似乎仍然是一个很好的模式)

Rau*_*ces 7

静态成员如是CONST_RELATED_TO_VALUE1相应枚举值的匿名类的成员,但不是枚举类本身的成员.与其他匿名类一样,VALUE1这里的对象被声明为类型,Test即使它是匿名子类的实例Test.

因此,您无法访问CONST_RELATED_TO_VALUE1via VALUE1.CONST_RELATED_TO_VALUE1因为VALUE1是类型的引用,Test并且CONST_RELATED_TO_VALUE1是匿名子类的成员但不是Test.CONST_RELATED_TO_VALUE1只能由匿名类的其他成员访问.

如果要访问匿名类中定义的值,则VALUE1需要m()在枚举对象的匿名类中重写枚举类型的方法(例如),并以某种方式返回或提供所需的值暴露(通过VALUE1.m()).


Psh*_*emo 2

VALUE1() {
  public static final String CONST_RELATED_TO_VALUE1 = "constant";
  public static final String OTHER_CONST_RELATED_TO_VALUE1 = "constant";
}
Run Code Online (Sandbox Code Playgroud)

是一个扩展枚举的匿名Test。对于此类,只有在创建后直接执行操作时,我们才能访问其成员(无需反射的帮助),如下所示:

class Foo{
    public static void main(String[] args) {
        System.out.println(new Foo(){
            public String x = "foo";
        }.x);
    }
}
Run Code Online (Sandbox Code Playgroud)

但如果我们写这样的东西:

Foo f = new Foo(){
    public String x = "foo";
};
System.out.println(f.x);
Run Code Online (Sandbox Code Playgroud)

我们会得到编译错误,因为f它的类型Foo没有x声明成员。
这就是你的枚举的问题。你在这里做了什么:

VALUE1() {
  public static final String CONST_RELATED_TO_VALUE1 = "constant";
  public static final String OTHER_CONST_RELATED_TO_VALUE1 = "constant";
}
Run Code Online (Sandbox Code Playgroud)

事实上是:

public static final Test VALUE1 = new Test(){
//                  ^^^^^^^^^^^
  public static final String CONST_RELATED_TO_VALUE1 = "constant";
  public static final String OTHER_CONST_RELATED_TO_VALUE1 = "constant";
}
Run Code Online (Sandbox Code Playgroud)

正如您所看到的VALUE1(和其他枚举常量)的类型Test,而不是Test$1(编译器给出的匿名类的名称)。

为什么Test选择类型Test$1?好吧,这可能是因为变量不能用匿名类型声明(我们不能有Test$1 foo变量),并且所有枚举类型实际上都编译成扩展 Enum 类的简单类,因此相同的规则必须适用于其字段(常量)。