Warning: file_get_contents(/data/phpspider/zhask/data//catemap/9/java/343.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Java接口静态变量未初始化_Java_Java 8_Initialization - Fatal编程技术网

Java接口静态变量未初始化

Java接口静态变量未初始化,java,java-8,initialization,Java,Java 8,Initialization,我正在经历对我来说毫无意义的奇怪行为。以下程序(我已尝试将其简化为最小示例)因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 但是,如果首先访问Ba

我正在经历对我来说毫无意义的奇怪行为。以下程序(我已尝试将其简化为最小示例)因
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
接口初始化和
fooneum
enum初始化之间存在循环依赖关系。通常,
FooEnum
初始化不会触发
Foo
接口初始化,但
Foo
具有默认方法

见:

当一个类被初始化时,它的超类被初始化(如果它们之前没有被初始化),以及声明任何默认方法的任何超接口(§8.1.5)(§9.4.3)

如果您想知道为什么默认方法会改变行为,我不知道强制这样做的真正理由。由于实现细节的原因,这更像是事后添加到规范中的(更改规范比更改JVM更容易)


因此,每当您有一个循环依赖项时,结果取决于首先访问哪个类型。首先访问的类型将等待另一个类初始值设定项的完成,但不会有递归

这可能不太明显,
Foo.A.baz()
具有这样的效果,但这会触发
FooEnum
的初始化,其中包含
switch
over
BarEnum
语句。每当类包含
枚举
开关
时,它的类初始值设定项都会为它准备一个表,因此,访问其初始值设定项中的
枚举
类型,导致初始化

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

你看,执行
Foo.A.baz()第一个在
Bar.Y.qux()之前
以与执行
Bar.Y.qux()不同的顺序触发初始化前一个
Foo.A.baz()

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


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

@Jobin我已添加了确切的输出。@Jobin这并不重要。我正在使用Intellij IDEA和terminal的普通Java编译器。“如果它是某种格式错误的代码,编译器不应该扔掉它吗?”-它不是格式错误。“AFAIK Java没有UB”-您是指未定义的行为吗?如果是,您显然没有阅读JLS 17。特别是JLS 17.4!这不是UB,因为行为是精确指定的,可以追溯发生了什么。我已经扩展了我的答案来描述这一点。所有符合条件的JVM都会这样做。@Stephen C:这仍然不是UB在
C
/
C++
的意义上,任何事情都可能发生。内存模型仍然定义了一个有限的可能发生的事情集,即使这个集合在一个数据竞争的复杂应用程序中会变得相当大。@Holger-你说得对。我是在回应这个评论。但是,如果我们吹毛求疵,如果您使用本机代码、
不安全的
、字节码工程、对
final
字段的某些反射操作以及可能的其他内容,那么真正的UB(在C/C++)意义上是可能的。@Stephen C:反射操作是非常明确的,即使以这种方式修改
final
字段。其他一切都在Java范围之外。否则,我们也应该提到硬件黑客…