我正在经历对我来说毫无意义的奇怪行为。以下程序(我试图将其简化为最小的示例)因NullPointerException
而崩溃,因为Bar。Y
为null
:
$ javac *.java
$ java Main
FooEnum.baz()
Exception in thread "main" java.lang.NullPointerException
at Main.main(Main.java:6)
我希望它打印:
FooEnum.baz()
Bar.qux
但是如果条。首先访问qux
(可以通过取消对main方法的第一行的注释或对以下两行重新排序来完成),程序将正确终止。
我怀疑这个问题与Java类初始化顺序有关,但我在相关JLS部分中找不到任何解释。
所以,我的问题是:这到底是怎么回事?这是某种虫子还是我错过了什么?
我的JDK版本是1.8.0_111
interface Bar {
// UPD
int barF = InitUtil.initInt("[Bar]");
Bar X = BarEnum.EX;
Bar Y = BarEnum.EY;
default void qux() {
System.out.println("Bar.qux");
}
}
enum BarEnum implements Bar {
EX,
EY;
// UPD
int barEnumF = InitUtil.initInt("[BarEnum]");
}
interface Foo {
Foo A = FooEnum.EA;
Foo B = FooEnum.EB;
// UPD
int fooF = InitUtil.initInt("[Foo]");
double baz();
double baz(Bar result);
}
enum FooEnum implements Foo {
EA,
EB;
// UPD
int fooEnumF = InitUtil.initInt("[FooEnum]");
public double baz() {
System.out.println("FooEnum.baz()");
// UPD this switch can be replaced with `return 42`
switch (this) {
case EA: return 42;
default: return 42;
}
}
public double baz(Bar result) {
switch ((BarEnum) result) {
case EX: return baz();
default: return 42;
}
}
}
public class Main {
public static void main(String[] args) {
// Bar.Y.qux(); // uncomment this line to fix NPE
Foo.A.baz();
Bar.Y.qux();
}
}
// UPD
public class InitUtil {
public static int initInt(String className) {
System.out.println(className);
return 42;
}
}
在Foo
接口初始化和fooneum
enum初始化之间存在循环依赖关系。通常,FooEnum
初始化不会触发Foo
接口初始化,但Foo
具有默认方法。
请参阅Java®语言规范§12.4.1。发生初始化时:
当一个类被初始化时,它的超类被初始化(如果它们以前没有被初始化),以及声明任何默认方法的任何超接口(§8.1.5)(§9.4.3)...
如果您想知道为什么默认方法会改变行为,我不知道强制这样做的真正理由。这看起来更像是事后添加到规范中,因为引用实现由于实现细节而表现出这种行为(更改规范比更改JVM更容易)。
因此,无论何时存在循环依赖关系,结果都取决于先访问哪个类型。首先访问的类型将等待另一个类初始化器的完成,但是不会有递归。
它可能不那么明显,Foo。a. baz();
有这样的效果,但是这会触发foEnum
的初始化,其中包含一个开关
overBarEnum
语句。每当一个类包含枚举
开关
时,它的类初始化器将为它准备一个表,因此,在它的初始化器中访问枚举
类型,导致它的初始化。
这就是为什么这会触发BarEnum
初始化,而这反过来又会触发Bar
初始化。相反,条。Y.qux()
语句首先直接访问条
,触发其初始化,然后触发BarEnum
的初始化。
你看,执行Foo。A.baz()
在
条之前的第一个。Y.qux()
以与执行
栏不同的顺序触发初始化。Y.qux()
在
Foo之前的第一个。A.baz()
。
如果首先访问
BarEnum
,则其类初始化将触发Bar
初始化,并将其自身的初始化延迟到Bar
初始化器完成。换句话说,在这种情况下,当Bar
初始值设定项运行时,enum
常量字段尚未写入,因此它将看到它们的null
值,并将这些null
引用复制到Bar
的字段。
如果首先访问
Bar
,它的类初始化将触发将写入枚举常量的BarEnum
初始化,因此在完成后,Bar
初始化器将看到正确初始化的值。
问题内容: 如何在Java中初始化类的私有静态成员。 尝试以下操作: 但是在创建类A的第二个对象然后调用f1()时,我得到了空指针异常。 问题答案: 初始化静态成员的首选方法是(如上所述) 或者对于更复杂的初始化代码,您可以使用静态初始化程序块:
问题内容: 我想知道为什么默认情况下C,C ++和Java中的确切静态变量初始化为零?为什么对局部变量不是这样? 问题答案: 为什么要对静态变量进行确定性初始化而对局部变量不进行初始化? 了解如何实现静态变量。 它们的内存在链接时分配,并且它们的初始值也在链接时提供。 没有运行时开销。 另一方面,用于局部变量的内存是在运行时分配的。堆栈必须增长。你不知道以前在那里。如果需要,可以清除该内存(将其清
问题内容: 我想知道静态变量何时初始化为其默认值。加载类时,先创建(分配)静态变量,然后执行静态初始化程序和声明中的初始化是否正确?在什么时候给出默认值?这导致前向参考的问题。 另外,如果你可以参考“ 为什么没有及时初始化静态字段?”这一问题进行解释,尤其是Kevin Brock在同一网站上给出的答案。我不明白第三点。 问题答案: 从请参阅Java静态变量方法: 它是一个属于类而不属于对象(实例)
问题内容: 当我运行此代码时,答案是1,我想应该是2。初始化的顺序和每一步中k的值是什么? 编辑1:作为后续的“ k设置为默认值”,那么为什么下一个代码不能编译?出现错误“在定义字段之前无法引用它”。 编辑2:出于某种我不知道的原因,它^可以在其“ Test.k”代替“ k”时使用。 感谢所有的答案。这将满足:D 问题答案: 它们按照您编写它们的顺序执行。如果代码是: 然后输出变为2。 初始化的顺
问题内容: 我有以下代码: 这给了我以下错误: 解析错误:语法错误,在第19行的/home/user/Sites/site/registration/inc/registration.class.inc中出现意外的’(’,期待’)’ 所以,我想我做错了什么…但是如果不那样做怎么办?如果我用常规字符串更改mktime内容,它将起作用。所以,我知道我能做到这一点 的那种 像.. 有人有指针吗? 问题答
问题内容: 当我试图写这样的东西: 编译器无法编译它。 但是当我写这样的东西时: 并反编译后,我看到了静态初始化: 您能否向我解释这种行为? 问题答案: 您可以具有静态初始化,但不能具有静态块。静态初始化需要静态代码块来实现的事实确实改变了Java语法。 关键是您不打算在接口中包含代码(在Java 8之前),但是可以初始化字段。 顺便说一句,您可以拥有一个嵌套的类或枚举,该类或枚举具有您想要的尽可