我理解为什么枚举构造函数无法访问静态字段 枚举本身内部和方法,以及为什么允许这样做 在课堂上。例如,以下代码为
import java.util.ArrayList;
import java.util.List;
public enum Foo {
A("Some string"),
B("Some other string"),
;
static List<String> list = new ArrayList<>();
Foo(String description) {
list.add(description);
}
}
此代码导致编译时错误illegal reference to static field from initializer
。
在静态字段包含所有内容之前调用枚举构造函数
已初始化。在上面的示例中,这意味着list
尚未初始化。这是因为静态字段在文本中初始化
订购(section 12.4.2)
接下来,执行类变量初始值设定项和静态 类的初始值设定项,或者字段的初始值设定项 界面,以文本顺序,就像它们是一个单独的块一样。
(强调我的)
因为枚举值本身总是先于任何其他值
字段,包括静态字段,它们不可用
枚举构造函数,即枚举之前不能有静态字段
值A
和B
。
然而,这是我的问题,为什么它是一个“私人”(封闭在一个类中)enum
可以访问其封闭类的静态字段,
无论枚举是否出现在---或---之前
在静态字段之后?特别是,在Java规范中指定了哪个?
请参阅以下代码以供参考
import java.util.ArrayList;
import java.util.List;
public class Bar {
static List<String> first = new ArrayList<>();
enum Baz {
A("Some string"),
B("Some other string"),
;
Baz(String description) {
// Can access static fields from before the enum
first.add(description);
// Can access static fields from _after_ the enum
second.add(description);
}
}
static List<String> second = new ArrayList<>();
}
答案 0 :(得分:4)
这在JLS中占有一席之地。 When Initialization Occurs chapter州
意图是类或接口类型具有一组初始化器 使它处于一致的状态,并且这个状态是第一个 其他类观察到的状态。
static
初始值设定项和 类变量初始值设定项以文本顺序执行,但可能不是 引用在声明中声明的类中的类变量 使用后以文本形式出现,即使这些类变量是 在范围内(§8.3.3)。此限制旨在在编译时检测 时间,大多数循环或其他错误的初始化。
该粗体代码段指的是直接包含访问权限的类。
enum
类型在Java语言规范here
枚举声明指定一个新的枚举类型,一种特殊的类类型。
您在Baz
构造函数
Baz(String description) {
// Can access static fields from before the enum
first.add(description);
// Can access static fields from _after_ the enum
second.add(description);
}
不是在类 中声明的类变量,即枚举类型Baz
。因此允许访问。
我们可以更深入地研究detailed class initialization procedure,这解释了每个类(类,接口,枚举)是独立初始化的。但是,我们可以创建一个看待尚未初始化的值的示例
public class Example {
public static void main(String[] args) throws Exception {
new Bar();
}
}
class Bar {
static Foo foo = Foo.A;
static Integer max = 42;
enum Foo {
A;
Foo() {
System.out.println(max);
}
}
}
这将打印null
。 max
类型的构造函数允许访问enum
,尽管我们的程序执行在max
初始化之前达到了访问权限。 JLS warns against this
初始化代码不受限制的事实允许示例 构造可以观察到类变量值的地方 当它在初始化之前仍然具有其初始默认值时 表达式被评估,但这样的例子在实践中很少见。 (这样 例如,也可以构造示例用于变量初始化 (§12.5)。)Java编程语言的全部功能可用 在这些初始化者中; 程序员必须小心谨慎。
您的原始Foo
示例引入了一项额外规则,该规则在chapter on Enum Body Declarations。
引用枚举类型的静态字段是编译时错误 来自构造函数,实例初始值设定项或实例变量 枚举类型的初始化表达式,除非该字段是a 常数变量(§4.12.4)。
该规则会阻止您的Foo
代码段进行编译。
enum
constants translate to public static final
fields。它们首先在enum
类型定义中以文本形式出现,因此首先进行初始化。它们的初始化涉及构造函数。存在的规则是为了防止构造函数看到其他类变量的未初始化值,这些值必须在以后初始化。
答案 1 :(得分:3)
嵌套类的位置没有意义。您可以在声明之前引用嵌套类(在源文本的前面),就像在声明它们之前可以引用方法一样。
这是因为嵌套类实际上是一个独立的类。封闭类和嵌套类是独立初始化的。
因此,我们可以说Bar
和Baz
都未初始化。
如果某些代码需要Bar
,那么Bar
将被初始化。由于Baz
未引用Bar
,因此Baz
不会在此时初始化。
但是,如果某些代码需要Baz
(它们都尚未初始化),则Baz
初始化开始。当Baz
的{{1}}构造函数开始运行时,A
仍未初始化。然后第一行需要Bar
,并开始Bar
初始化。 Bar
初始化在执行Bar
语句之前正常完成。第二个语句也可以执行,因为first.add(description)
已完全初始化。
如您所见,没有初始化顺序冲突。 Bar
将完全初始化,因此完全可用,供Bar
构造函数使用。
为了查看事件的顺序,我添加了一些打印语句:
Baz
输出
public class Test {
public static void main(String[] args) {
Bar.Baz x = Bar.Baz.A;
}
}
class Bar {
static { System.out.println("Bar initializing..."); }
static List<String> first = new ArrayList<>();
enum Baz {
A("Some string"),
B("Some other string"),
;
static { System.out.println("Baz initializing..."); }
Baz(String description) {
System.out.println(getClass() + "." + name() + " in construction...");
// Can access static fields from before the enum
first.add(description);
// Can access static fields from _after_ the enum
second.add(description);
System.out.println(getClass() + "." + name() + " constructed...");
}
static { System.out.println("Baz initialized..."); }
}
static List<String> second = new ArrayList<>();
static { System.out.println("Bar initialized"); }
}
如您所见,class Bar$Baz.A in construction...
Bar initializing...
Bar initialized
class Bar$Baz.A constructed...
class Bar$Baz.B in construction...
class Bar$Baz.B constructed...
Baz initializing...
Baz initialized...
将在Bar
构造函数中开始初始化 ,并且此时将完全初始化。因此,无论源文本位置如何,它都可供Baz
完全使用。
您还可以看到Baz
静态初始值设定项在之后构建枚举后才会运行,这当然是您无法从构造函数中引用静态成员的原因。