我最近在工作中遇到过这个问题。虽然我不确定这是一个好主意,但我不明白编译器如何处理静态块。
以下是一个例子:
考虑您有课程A
和B
:
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
在其他地方可能被使用之前被初始化的机制呢?
答案 0 :(得分:34)
该机制详细描述here,但最重要的五点是:
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;
}
这里的结果是2 1
,因为初始化的方式是:
Bar
初始化开始。Bar.X
的初始值,这需要先初始化Foo
Foo
初始化开始。Foo.X
的初始值,但由于Bar
初始化已在进行中,因此不会再次初始化Bar.X
s&#34;当前&#34 ;使用的值为0,因此Foo.X
初始化为1。Bar.X
的值,Foo.X
为1,因此Bar.X
变为2。即使两个字段都被声明为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之后执行,如上所述,即现在引用的静态变量/方法和静态块都将被执行。