如何解决静态块之间的对象依赖关系?

2022-09-01 07:19:54

我最近在工作中遇到了这个问题。虽然我不确定这是否真的是一个好主意,但我不明白编译器如何处理静态块。

下面是一个示例:

假设您有类和:AB

public class A {

    public final static List<Integer> list;
    static {
        list = new ArrayList<>();
    }
}

public class B {

    public final static int dependsOnA;
    static {
        dependsOnA = A.list.size();
    }
}

以及一个刚刚读取的主类。B.dependsOnA

中的静态块与 中的静态块相关,因为它使用静态变量。BAlist

现在,代码可以正确执行,并且在运行时不会引发任何代码。但是,在可能在其他地方使用之前,确保对其进行初始化的机制是什么?NullPointerExceptionlist


答案 1

这里详细介绍了该机制,但最重要的五点是:

  1. 在引用类之前,需要对其进行初始化。
  2. 如果类的初始化已经开始(或者如果已完成),则不会再次尝试。
  3. 在初始化类之前,需要首先初始化其所有超类和超接口。
  4. 单个类中的静态初始化器按文本顺序执行。
  5. 实现的接口按照它们在子句中出现的顺序进行初始化。implements

这些规则完全定义了静态块的执行顺序。

您的情况相当简单:在您访问 之前,需要初始化(规则1),然后静态初始化器尝试访问,这会触发类的初始化(同样是规则1)。B.dependsOnABA.listA

请注意,没有什么可以阻止您以这种方式创建循环依赖项,这将导致有趣的事情发生:

public class Bar {
    public static int X = Foo.X+1;

    public static void main(String[] args) {
        System.out.println( Bar.X+" "+Foo.X); // 
    }

}

class Foo {
    public static int X = Bar.X+1;
}

这里的结果是,初始化的方式是这样的:2 1

  1. Bar的初始化开始。
  2. Bar.X的初始值被评估,这需要先初始化Foo
  3. Foo的初始化开始。
  4. Foo.X评估了 s 的初始值,但由于 s 初始化已经在进行中,因此不会再次初始化,而是使用“当前”值,即 0,因此初始化为 1。BarBar.XFoo.X
  5. 我们回到评估值,是1,所以变成2。Bar.XFoo.XBar.X

即使两个字段都已声明,这也有效。final

这个故事的寓意是要小心静态初始化器引用同一库或应用程序中的其他类(引用第三方库或标准类库中的类是安全的,因为它们不会引用你的类)。


答案 2

“机制”是 JVM 的类装入器,它将确保在将控制流返回到类首次引用的位置之前执行类的初始化块(在整个 JVM 中使用全局锁)。只有在引用之后,它才会首先加载类,在这种情况下,当 的 init 块引用时。ABA.list


推荐