为什么可以在Java中通过方法访问定义之前的静态字段?

16

我遇到了一件有趣的事情:

static {
    System.out.println(test);     // error cannot reference a field before it is defined
    System.out.println(cheat());  // OK! 
}

private static boolean cheat() {
    return test;
}

private static boolean test = true;

public static void main(String args[]) {}

第一种方法是错误的,你的编译器和IDE会告诉你它是错误的。在第二种情况下,作弊是可以的,但它实际上将字段test默认设置为false。使用Sun JDK 6。

3个回答

11
这在JLS 8.3.2.3中有定义。具体来说:

如果成员的使用出现在 C 的静态初始化程序中,[...] 则需要在文本上先声明该成员再使用它。

当调用cheat()时,您就绕过了这个规则。实际上,这是该节例子的第五个示例
请注意,在静态初始化块中cheat()将返回false,因为test尚未初始化。

+1. 我正在寻找这个参考资料,但那时我得到了负评。 - kosa
@Andremoniy 我正在写那个。 - assylias
@assylias,Java 6有类似的示例吗? - mvd
@mdobrinin 它的工作方式相同 - 这个部分已经是 JLS v5 的一部分,因此在这方面自 Java 5 以来没有任何变化。 - assylias

3
因为类加载的顺序如下:
  • 加载类定义(方法,签名)
  • 为静态变量引用(对于 test)分配内存 - 尚未初始化
  • 执行static初始化程序(对于变量)和static块 - 按照定义的顺序
因此,当您到达static块时,您已经准备好了方法定义,但尚未准备好变量。使用cheat()实际上是在读取未初始化的值。

1
这是类加载的通用步骤:
  1. 加载 - 将类加载到内存中
  2. 验证 - 检查类的二进制表示是否正确
  3. 准备 - 为类创建静态字段,并将这些字段初始化为它们的标准默认值。
  4. 初始化 - 将调用静态初始程序和静态字段的初始化器
准备完成后,您的测试结果将为false。然后在将静态变量分配为true之前,将执行静态块。这就是为什么您会得到false的原因。
尝试将静态变量设置为final。在这种情况下,您将获得true。这是因为编译器本身将嵌入字节码中的值(因为该字段是final)作为优化的一部分。

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