【问题标题】:Why does a static variable initialized by a method call that returns another static variable remains null? [duplicate]为什么由返回另一个静态变量的方法调用初始化的静态变量仍然为空? [复制]
【发布时间】:2018-01-30 06:38: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。我更感兴趣的是了解为什么会发生这种情况,而不是解决方案。

【问题讨论】:

    标签: java static-variables


    【解决方案1】:

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

    static String s1 = getVal();
    

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

    【讨论】:

      【解决方案2】:

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

      s1s2之前被初始化,因此对getVal()的调用返回s2的默认值,即null

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

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

      另一种强制s2 的初始化在s1 之前进行的方法是使s2 成为final:

      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. 如果 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. 接下来,按照文本顺序执行类的变量初始化器和静态初始化器,或者接口的字段初始化器,就好像它们是单个块一样

      【讨论】:

        【解决方案3】:

        根据JLS 12.4.2 节,静态字段的初始化如下:

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

        所以先初始化s1,此时s2没有初始化,所以它的默认值为String,即null

        然后s2 被初始化为"S2"s1 保持null

        只需更改两个声明的顺序即可解决此问题。

        【讨论】:

          【解决方案4】:

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

          【讨论】:

            猜你喜欢
            • 2018-03-15
            • 2016-03-05
            • 1970-01-01
            • 2019-03-04
            • 2016-07-27
            • 2017-05-13
            • 2011-08-22
            • 2010-12-22
            • 1970-01-01
            相关资源
            最近更新 更多