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

问题描述 投票:0回答:3

我想知道为什么,虽然在 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;
}

使用

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

产生以下输出

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

很明显,我们实际上在运行时为

VALUE1
VALUE2
提供了适当的专用子类。但看起来我们丢失有关
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                                                                                                                                    

所有周围的代码仍然可以工作。此外,我们还可以做我最初尝试的事情,通过

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                                                                                                                                    

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

现在我知道必须有一些小的改变,但是从匿名类到命名类并且不丢弃类型似乎是一个小改变,这看起来是一个相当不错的功能,但没有一种简单的方法来使用它来模拟它覆盖成员或其他东西。

所以我想知道为什么除了时间之外没有像这样实现?我是否在这里遗漏了一些关键的东西,这些东西会阻止编译器执行此操作(无论是关于实现复杂性还是类型系统的不可能性),只是没有实现它以使其更简单,因为没有时间或类似的东西?

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

java types enums
3个回答
7
投票

诸如

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

因此,您无法通过

CONST_RELATED_TO_VALUE1
访问
VALUE1.CONST_RELATED_TO_VALUE1
,因为
VALUE1
Test
类型的引用,并且
CONST_RELATED_TO_VALUE1
是匿名子类的成员,但不是
Test
的成员。
CONST_RELATED_TO_VALUE1
只能被匿名类的其他成员访问。

如果要访问

VALUE1
的匿名类中定义的值,则需要有一个枚举类型的方法(例如
m()
),您可以在枚举对象的匿名类中重写该方法,并返回或以某种方式提供您想要公开的价值(通过
VALUE1.m()
)。


3
投票

枚举常量是特殊变量,其类型与声明枚举类型相同。换句话说,引用表达式

Test.VALUE1
的类型为
Test
。类型
TEST
未定义变量名称
CONST_RELATED_TO_VALUE1
,因此您无法访问变量名称。

这与做类似

class Parent {
}

class Child extends Parent {
    public Object field = new Object();
}
...
Parent ref = new Child(); 
System.out.println(ref.field); // compilation error

除非您尝试通过引用表达式访问

static
字段。


枚举常量的可选主体定义了扩展枚举类型的新匿名类。


2
投票
VALUE1() {
  public static final String CONST_RELATED_TO_VALUE1 = "constant";
  public static final String OTHER_CONST_RELATED_TO_VALUE1 = "constant";
}

是一个匿名类,它扩展了

Test
枚举。对于此类,我们只能在创建后直接访问其成员(无需反射的帮助),例如:

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

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

Foo f = new Foo(){
    public String x = "foo";
};
System.out.println(f.x);

我们会得到编译错误,因为

f
Foo
类型,它没有声明
x
成员。
这就是你的枚举的问题。你在这里做了什么:

VALUE1() {
  public static final String CONST_RELATED_TO_VALUE1 = "constant";
  public static final String OTHER_CONST_RELATED_TO_VALUE1 = "constant";
}

事实上是:

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

所以如你所见,

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

为什么选择

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

© www.soinside.com 2019 - 2024. All rights reserved.