java 接口初始化_Java接口静态变量未初始化

我正在经历一种对我来说没有意义的奇怪行为.以下程序(我试图将其简化为最小的示例)与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

但是,如果首先访问Bar.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接口初始化和FooEnum枚举初始化之间存在循环依赖关系.通常,FooEnum初始化不会触发Foo接口初始化,但Foo具有默认方法.

When a class is initialized, its superclasses are initialized (if they have not been previously initialized), as well as any superinterfaces (§8.1.5) that declare any default methods (§9.4.3)…

如果你想知道为什么默认方法会改变行为,我不知道要求这个的真正原理.事实上,由于实现细节(并且更改规范比更改JVM更容易),事实上,because the reference implementation exhibited this behavior更像是添加到规范中.

因此,只要有循环依赖关系,结果就取决于首先访问的类型.首先访问的类型将等待另一个类初始值设定项的完成,但不会有递归.

Foo.A.baz();可能不那么明显;有这样的效果,但这会触发FooEnum的初始化,它包含一个BarEnum语句的切换.每当一个类包含一个枚举开关时,它的类初始化器将为它准备一个表,因此,在其初始化器中访问枚举类型,从而导致其初始化.

这就是为什么这会触发BarEnum初始化,从而触发Bar初始化.相比之下,Bar.Y.qux();语句首先直接访问Bar,触发其初始化,从而触发BarEnum的初始化.

所以你看,执行Foo.A.baz();首先在Bar.Y.qux()之前;以不同于执行Bar.Y.qux()的顺序触发初始化;首先在Foo.A.baz();之前.

如果首先访问BarEnum,其类初始化将触发Bar初始化并推迟其自己的初始化,直到Bar初始化程序完成.换句话说,在这种情况下,当Bar初始化程序运行时,尚未写入枚举常量字段,因此它将看到它们的空值并将这些空引用复制到Bar的字段.

如果首先访问Bar,它的类初始化将触发BarEnum初始化,该初始化将写入枚举常量,因此在完成时,Bar初始值设定项将看到正确初始化的值.

标签:java,java-8,initialization

来源: https://codeday.me/bug/20190724/1523955.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值