为什么由返回另一个静态变量的方法调用初始化的静态变量保持为空?

时间:2018-01-30 06:38:18

标签: java static-variables

我根本不理解以下代码的执行流程:

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。我更感兴趣的是理解为什么会发生这种情况而不是解决方案。

4 个答案:

答案 0 :(得分:16)

静态事物按照它们在代码中出现的顺序执行。

static String s1 = getVal();

从第一行开始,s1会被评估,到那时s2仍为null。因此,您会看到空值。

答案 1 :(得分:13)

static变量和static初始化程序块按照它们在源代码中出现的顺序进行初始化(static final变量除外,这些变量在非最终static变量之前初始化)。

s1s2之前初始化,因此对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的过程如下:

     
      
  1. 在C的初始化锁定LC上同步。这涉及等到当前线程可以获取LC。

  2.   
  3. 如果C的Class对象指示某个其他线程正在对C进行初始化,则释放LC并阻止当前线程,直到通知正在进行的初始化已完成,此时重复此步骤

  4.   
  5. 如果C的Class对象指示当前线程正在为C进行初始化,那么这必须是初始化的递归请求。释放LC并正常完成。

  6.   
  7. 如果C的Class对象表明C已经初始化,则不需要进一步的操作。释放LC并正常完成。

  8.   
  9. 如果C的Class对象处于错误状态,则无法进行初始化。释放LC并抛出NoClassDefFoundError。

  10.   
  11. 否则,记录当前线程正在进行C的Class对象初始化并释放LC的事实。

         

    然后,初始化C的静态字段,这些字段是常量变量(§4.12.4,§8.3.2,§9.3.1)。

  12.   
  13. 接下来,如果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导致的同一异常

  14.   
  15. 接下来,通过查询定义的类加载器来确定是否为C启用了断言(§14.10)。

  16.   
  17. 接下来,以文本顺序执行类的类变量初始值设定项和静态初始值设定项,或接口的字段初始值设定项,就像它们是单个块一样

  18.   

答案 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
    }
}