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

2022-09-03 04:20:37

我只是不明白以下代码的执行流程:

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

它应该在第二个语句处打印。我更感兴趣的是理解为什么会发生这种情况,而不是一个解决方案。S2println


答案 1

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

static String s1 = getVal();

因此,从第一行开始,进行评估,到那时仍然是 。因此,您会看到空值。s1s2null


答案 2

static变量和初始值设定项块按照它们在源代码中出现的顺序进行初始化(在非最终变量之前初始化的变量除外)。staticstatic finalstatic

s1在 之前初始化,因此 对 的调用返回默认值 ,即 。s2getVal()s2null

您可以更改变量的顺序,以便首先初始化:statics2

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

另一种强制初始化发生在 之前发生的方法是使 final:s2s1s2

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 子句的从左到右的顺序)。

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

    如果 S 的初始化由于引发的异常而突然完成,则获取 LC,将 C 的 Class 对象标记为错误,通知所有等待线程,释放 LC,然后突然完成,引发初始化 S 时导致的相同异常。

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

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


推荐