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

时间:2015-05-12 13:24:39

标签: 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();
    }
}

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

B中的静态块依赖于A中的静态块,因为它使用list静态变量。

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

7 个答案:

答案 0 :(得分:34)

该机制详细描述here,但最重要的五点是:

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

    您的情况相当简单:在您访问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;
    }
    

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

    1. Bar初始化开始。
    2. 评估
    3. Bar.X的初始值,这需要先初始化Foo
    4. Foo初始化开始。
    5. 评估
    6. Foo.X的初始值,但由于Bar初始化已在进行中,因此不会再次初始化Bar.X s&#34;当前&#34 ;使用的值为0,因此Foo.X初始化为1。
    7. 我们回到评估Bar.X的值,Foo.X为1,因此Bar.X变为2。
    8. 即使两个字段都被声明为final,这也有效。

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

答案 1 :(得分:9)

&#34;机制&#34;是JVM的类加载器,它将确保一个类&#39;在将控制流返回到首次引用类的位置之前,执行初始化块(在整个JVM上使用全局锁定)。它只会在引用后加载类A,在这种情况下,当B的初始块引用A.list时。

答案 2 :(得分:8)

在执行@import url(font-awesome.min.css); static块期间,运行时第一次遇到B,它将调用A static块在访问A之前。

答案 3 :(得分:7)

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

当您说B.dependsOnA时,B类开始通过JVM加载geting,static中的B块在此过程中被调用。当您说dependsOnA = A.list.size();时,类A开始由JVM加载,并且A中的static块将在此过程中执行,初始化list。只有在JVM完全加载了类list.size()之后,才会执行语句A。随后,JVM只能在B中的静态块完成后完成加载类B

答案 4 :(得分: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();
    }
}

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

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

    public static void main(String[] args)
    {
        System.out.println(B.dependsOnA);
    }
}

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

Loaded Class C
Loaded Class B
Loaded Class A
0

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

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

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

希望这有帮助。

答案 5 :(得分:5)

这里我们有一些解释 Static Block in Java

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

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

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

答案 6 :(得分:5)

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