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

17

我只是不理解以下代码的执行流程:

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

第二个println语句应该打印S2。我更感兴趣的是了解为什么会这样,而不是解决方案。

4个回答

19

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

static String s1 = getVal();

首先从第一行开始,s1被评估,在这个时候s2仍然是null。因此你看到了空值。


16

static变量和static初始化块的初始化顺序是按照它们在源代码中出现的顺序进行的(但static final变量会在非final的static变量之前进行初始化)。

s1先被初始化,因此调用getVal()方法返回的是s2的默认值,即null

您可以改变static变量的顺序以便先初始化s2

static String s2 = "S2";
static String s1 = getVal();

强制初始化s2s1之前的另一种方式是将s2声明为final:

static String s1 = getVal();
static final String s2 = "S2";

以下内容摘自JLS 12.4.2详细的初始化过程,其中与static变量相关的部分已被突出显示。

对于每个类或接口C,都有一个唯一的初始化锁LC。从C到LC的映射留给Java虚拟机实现决定。然后初始化C的过程如下:

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

  2. 如果C的Class对象指示其他线程正在初始化C,则释放LC并阻塞当前线程,直到收到通知,表明正在进行中的初始化已完成,然后重复此步骤。

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

  4. 如果C的Class对象指示C已经初始化,则不需要采取进一步措施。释放LC并正常完成。

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

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

    接下来,初始化C的静态字段,这些字段是常量变量(§4.12.4、§8.3.2、§9.3.1)。

  7. 然后,如果C是一个类而不是一个接口,则让SC成为其超类,并让SI1、...、SIn成为声名至少一个默认方法的C的所有超接口。超接口的顺序由对C直接实现的每个接口的超接口层次结构进行递归枚举(按照C的implements子句的从左到右的顺序)给出。对于C直接实现的每个接口I,枚举在I的超接口(按照I的extends子句的从左到右的顺序)上递归,然后返回I。

    对于列表[ SC,SI1,...,SIn ]中的每个S,如果S尚未初始化,则递归执行S的整个过程。必要时首先验证和准备S。

    如果因抛出异常而使S的初始化中止,则获取LC,将C的Class对象标记为错误,通知所有等待的线程,释放LC,并突然完成,抛出与初始化S导致的相同的异常。

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

  9. 接下来,按文本顺序执行类的变量初始化器和静态初始化器,或者执行接口的字段初始化器,就好像它们是一个单独的块一样


2

s1首先被初始化,此时s2的值为null。 s1试图返回稍后初始化的s2的值。 这就是你得到null值的原因。

如果你尝试如下操作,你将会得到期望的答案“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
    }
}

2
根据JLS第12.4.2节,静态字段的初始化如下:
执行类变量初始化程序和静态初始化程序或接口的字段初始化程序,按文本顺序执行,就像它们是单个块一样。
因此,首先初始化s1,此时s2未初始化,因此它具有String的默认值null。
然后s2被初始化为“S2”,但s1仍保持null。
只需更改两个声明的顺序即可解决此问题。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接