当前位置: 首页 > 知识库问答 >
问题:

Java接口静态变量未初始化

乜飞航
2023-03-14

我正在经历对我来说毫无意义的奇怪行为。以下程序(我试图将其简化为最小的示例)因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

但是如果条。首先访问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;
    }
}

共有1个答案

戚学文
2023-03-14

Foo接口初始化和fooneumenum初始化之间存在循环依赖关系。通常,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之前),但是可以初始化字段。 顺便说一句,您可以拥有一个嵌套的类或枚举,该类或枚举具有您想要的尽可