Java接口静态变量未初始化

沃托普尔

我正在经历对我来说没有意义的奇怪行为。以下程序(我已尝试将其简化为最少的示例)崩溃,NullPointerException原因Bar.Ynull

$ 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具有默认方法

请参阅Java®语言规范,第12.4.1节。发生初始化时

初始化一个类时,将初始化其超类(如果之前尚未对其进行初始化)以及声明任何默认方法的任何超接口(第8.1.5节)(第9.4.3节)……

如果您想知道为什么默认方法会更改行为,我不知道强制执行此操作的真正理由实际上,这似乎是在事实之后添加到规范中的,因为参考实现由于实现细节而表现出了这种行为(更改规范比更改JVM更容易)。


因此,只要您具有循环依赖关系,结果就取决于首先访问哪种类型。首先访问的类型将等待其他类初始化程序的完成,但不会进行递归。

可能没有那么明显Foo.A.baz();的作用,但这会触发FooEnum包含switchoverBarEnum语句的初始化每当一个类包含一个时enum switch,它的类初始化器都会为其准备一个表,从而enum在其初始化器中直接访问该类型,从而导致其初始化。

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

因此,您可以看到,执行Foo.A.baz();first beforeBar.Y.qux();会以不同于执行Bar.Y.qux();first before的顺序触发初始化Foo.A.baz();

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

如果Bar首先访问它,则其类初始化将触发BarEnum初始化,初始化将写入枚举常量,因此完成后,Bar初始化器将看到正确的初始化值。

本文收集自互联网,转载请注明来源。

如有侵权,请联系 [email protected] 删除。

编辑于
0

我来说两句

0 条评论
登录 后参与评论

相关文章