我想知道为什么,虽然在 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
这甚至为调试提供了额外的好处,即内部子类名称清楚地映射到相应的枚举值。
现在我知道必须有一些小的改变,但是从匿名类到命名类并且不丢弃类型似乎是一个小改变,这看起来是一个相当不错的功能,但没有一种简单的方法来使用它来模拟它覆盖成员或其他东西。
所以我想知道为什么除了时间之外没有像这样实现?我是否在这里遗漏了一些关键的东西,这些东西会阻止编译器执行此操作(无论是关于实现复杂性还是类型系统的不可能性),只是没有实现它以使其更简单,因为没有时间或类似的东西?
(我主要是从编译器/类型系统的角度寻找为什么决定这样实现它的原因,而不是寻找实际的替代方案,因为肯定有几个,尽管它看起来仍然是一个不错的模式有)
诸如
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()
)。
枚举常量是特殊变量,其类型与声明枚举类型相同。换句话说,引用表达式
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
字段。
枚举常量的可选主体定义了扩展枚举类型的新匿名类。
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 类的简单类,因此必须适用相同的规则它的字段(常量)。