如何解析静态块之间的对象依赖性?

nsa*_*lar 33 java jvm classloader

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

这是一个例子:

考虑你有类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();
    }
}
Run Code Online (Sandbox Code Playgroud)

还有一个只读的主要课程B.dependsOnA.

静态块B依赖于in A,因为它使用list静态变量.

现在,代码正确执行,并且NullPointerException在运行时不会引发.但是什么是确保list在其他地方潜在使用之前进行初始化的机制是什么?

biz*_*lop 34

这里详细描述了这种机制,但最重要的五点是:

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

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

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

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

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;
}
Run Code Online (Sandbox Code Playgroud)

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

  1. Bar初始化开始了.
  2. Bar.X评估初始值,这需要Foo先进行初始化
  3. Foo初始化开始了.
  4. Foo.X评估初始值,但由于Bars初始化已在进行中,因此不会再次初始化,使用Bar.Xs"当前"值,即0,因此Foo.X初始化为1.
  5. 我们回到评估Bar.Xs值,Foo.X是1因此Bar.X变为2.

即使声明了两个字段,这也有效final.

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


Gru*_*eck 9

"机制"是JVM的类加载器,它将确保在将控制流返回到首次引用类的位置之前执行类的初始化块(在整个JVM上具有全局锁定).它将首先A在引用后加载类,在这种情况下B是init块引用A.list.


Glo*_*del 8

在执行static块时B,运行时A第一次遇到,它将在访问之前调用static块.AA.list


CKi*_*ing 7

无论你如何编写代码,static块都是一个static块,它将作为加载类的JVM的一部分执行.

当你说B.dependsOnA,B类开始由JVM加载geting,并且在这个过程staticB会在某处调用块.当你说dependsOnA = A.list.size();,类A开始被JVM加载,并且staticA中的块将在这个初始化的过程中的某个地方执行list.list.size()只有A在JVM完全加载了类之后,才会执行该语句.随后,JVM只能B 在B中的静态块完成后完成加载类.


Sri*_*ati 6

这是类加载器的工作.java中的类加载从bootstrap类加载器开始.这个类加载器首先加载标准java库中的所有类,即rt.jar.

然后调用扩展类加载器.这将加载安装在JVM ext目录中的扩展jar文件中的所有类.现在最后调用classpath类加载器.

classpath类加载器开始从主类加载类,主类是定义了main方法的类.加载后,它会在该类中执行任何静态初始值设定项.在执行初始化程序时,如果遇到任何未加载的类,它将暂停执行静态块,首先加载该类,最后恢复该静态块的执行.

因此,不可能发生对非加载类的任何调用.让我们看看你自己的例子,其代码如下:

class A
{
    public final static List<Integer> list;
    static
    {
        System.out.println("Loaded Class A");
        list = new ArrayList<>();
    }
}

class B
{
    public final static int dependsOnA;
    static
    {
        System.out.println("Loaded Class B");
        dependsOnA = A.list.size();
    }
}
Run Code Online (Sandbox Code Playgroud)

在这个例子中,实际上没有main方法,所以这些类实际上不会被加载到内存中.假设,让我们将以下主类添加到上面的代码中.

class C
{
    static
    {
        System.out.println("Loaded Class C");
    }

    public static void main(String[] args)
    {
        System.out.println(B.dependsOnA);
    }
}
Run Code Online (Sandbox Code Playgroud)

让我们看看它会在输出中产生什么:http://ideone.com/pLg3Uh

Loaded Class C
Loaded Class B
Loaded Class A
0
Run Code Online (Sandbox Code Playgroud)

也就是说,首先加载C类,因为它有主要方法.加载后,将调用C类的静态初始值设定项.但请注意,在加载C类的静态块之后调用main方法.

现在是main方法,我们打印了dependsOnA类B 的值.现在,类加载器停止执行该语句,并加载类B,并执行它的静态块,而静态块又为dependsOnA变量赋值的值为A类列表中的元素,未加载.

所以类加载器从那里跳转,现在加载类,并调用类A的静态块,并创建一个列表.现在由于没有更多的类要加载,类加载器返回到B类的静态块,并且分配完成.最后,控件现在使用main方法,并将值dependsOnA打印到控制台.

希望这可以帮助.


小智 5

这里我们在Java中有一些解释 静态块

如果先调用A类,则调用A静态并且A.list存在,并且当B调用它时.

如果先调用B类,则调用B静态,级联到A调用,调用其静态块,其中创建了A.list.

我们可以看到它是最棘手的方式:B> B.static> A> A.static> A.list存在


Vir*_*ade 5

工作是非常简单的JVM类加载器,它将确保在首次引用类时执行类静态块.
1.如果静态块中有可执行语句,JVM将在将类加载到JVM时自动执行这些语句.
2.如果你从静态块中引用一些静态变量/方法,这些语句将在类加载到JVM之后执行,如上所述,即现在引用的静态变量/方法和静态块都将被执行.