我根本不理解以下代码的执行流程:
class Test {
static String s1 = getVal();
static String s2 = "S2";
private static String getVal() {
return s2;
}
public static void main(String args[]) {
System.out.println(s2); // prints S2
System.out.println(s1); // prints null
}
}
应该在第二个S2
语句中打印println
。我更感兴趣的是理解为什么会发生这种情况而不是解决方案。
答案 0 :(得分:16)
静态事物按照它们在代码中出现的顺序执行。
static String s1 = getVal();
从第一行开始,s1
会被评估,到那时s2
仍为null
。因此,您会看到空值。
答案 1 :(得分:13)
static
变量和static
初始化程序块按照它们在源代码中出现的顺序进行初始化(static final
变量除外,这些变量在非最终static
变量之前初始化)。
s1
在s2
之前初始化,因此对getVal()
的调用会返回默认值s2
,即null
。
您可以更改static
变量的顺序,以便首先初始化s2
:
static String s2 = "S2";
static String s1 = getVal();
强制s2
初始化s1
之前的另一种方法是让s2
成为最终版本:
static String s1 = getVal();
static final String s2 = "S2";
以下是JLS 12.4.2. Detailed Initialization Procedure中所述的初始化顺序的摘录。与static
变量相关的部分会突出显示。
对于每个类或接口C,都有一个唯一的初始化锁定LC。从C到LC的映射由Java虚拟机实现决定。初始化C的过程如下:
在C的初始化锁定LC上同步。这涉及等到当前线程可以获取LC。
如果C的Class对象指示某个其他线程正在对C进行初始化,则释放LC并阻止当前线程,直到通知正在进行的初始化已完成,此时重复此步骤
如果C的Class对象指示当前线程正在为C进行初始化,那么这必须是初始化的递归请求。释放LC并正常完成。
如果C的Class对象表明C已经初始化,则不需要进一步的操作。释放LC并正常完成。
如果C的Class对象处于错误状态,则无法进行初始化。释放LC并抛出NoClassDefFoundError。
否则,记录当前线程正在进行C的Class对象初始化并释放LC的事实。
然后,初始化C的静态字段,这些字段是常量变量(§4.12.4,§8.3.2,§9.3.1)。
接下来,如果C是一个类而不是一个接口,那么让SC成为它的超类,让SI1,...,SIn成为C的所有超接口,声明至少一个默认方法。超接口的顺序由直接由C实现的每个接口的超接口层次结构的递归枚举给出(以C的implements子句的从左到右的顺序)。对于我直接用C实现的每个接口,在返回I之前,枚举在I的超接口上以I的extends子句的从左到右的顺序重复出现。
对于列表中的每个S [SC,SI1,...,SIn],如果S尚未初始化,则递归执行S的整个过程。如有必要,首先验证并准备S.
如果S的初始化因抛出异常而突然完成,则获取LC,将C对象标记为错误,通知所有等待线程,释放LC,然后突然完成,抛出初始化S导致的同一异常
接下来,通过查询定义的类加载器来确定是否为C启用了断言(§14.10)。
- 醇>
接下来,以文本顺序执行类的类变量初始值设定项和静态初始值设定项,或接口的字段初始值设定项,就像它们是单个块一样。
答案 2 :(得分:2)
根据JLS第12.4.2节,静态字段的初始化如下:
接下来,执行类变量初始化程序和类的静态初始化程序,或接口的字段初始值设定项,以文本顺序,就像它们是单个块。
首先初始化s1
,此时s2
未初始化,因此它的默认值为String
,即null
。
然后s2
初始化为"S2"
,但s1
保持null
。
只需更改两个声明的顺序即可解决此问题。
答案 3 :(得分:2)
s1 ,此时 s2 的值为空。 s1 正在尝试返回稍后已初始化的 s2 的值。 这就是为什么你把它变成空的原因。
如果您尝试这样做,您将得到预期的答案' S2'
class Test {
static String s2 = "S2";
static String s1 = getVal();
private static String getVal() {
return s2;
}
public static void main(String args[]) {
System.out.println(s2); // prints S2
System.out.println(s1); // prints S2
}
}