为什么声明的顺序对静态初始化器很重要?

时间:2009-07-27 10:20:54

标签: java language-features

我有这段代码

private static Set<String> myField;

static {
    myField = new HashSet<String>();
    myField.add("test");
}

它有效。但是当我翻转订单时,我收到非法转发参考错误。

static {
    myField = new HashSet<String>();
    myField.add("test"); // illegal forward reference
}

private static Set<String> myField;

我有点震惊,我没想到Java有这样的东西。 :)

这里发生了什么?为什么声明的顺序很重要?为什么赋值工作而不是方法调用?

6 个答案:

答案 0 :(得分:10)

首先,让我们讨论一下“前向参考”是什么以及为什么它是坏的。前向引用是对尚未初始化的变量的引用,并且它不仅限于静态初始化器。这些都很糟糕,因为如果允许的话,它们会给我们带来意想不到的结果。看看这段代码:

public class ForwardRef {
    int a = b; // <--- Illegal forward reference
    int b = 10;
}

这个类初始化时应该是什么?初始化类时,将按照从第一个到最后一个遇到的顺序执行初始化。因此,你期望这一行

a = b; 

在执行之前执行:

b = 10; 

为了避免这种问题,Java设计者完全不允许使用前向引用。

修改

此行为由section 8.3.2.3 of Java Language Specifications指定:

  

成员的声明只有在成员是类或接口C的实例(分别是静态)字段并且满足以下所有条件时才需要出现:

     
      
  • 用法发生在C的实例(分别是静态)变量初始值设定项或C的实例(分别是静态)初始值设定项中。

  •   
  • 用法不在作业的左侧。

  •   
  • C是封闭用法的最里面的类或接口。

  •   
     

如果不满足上述三个要求中的任何一个,则会发生编译时错误。

答案 1 :(得分:2)

试试这个:

class YourClass {
    static {
        myField = new HashSet<String>();
        YourClass.myField.add("test");
    }

    private static Set<String> myField;
}

它应该根据JLS编译而没有错误...
(真的没有帮助,或者?)

答案 2 :(得分:1)

在Java中,所有初始值设定项(静态或其他)都按它们在类定义中出现的顺序进行评估。

答案 3 :(得分:1)

请参阅the JLS中的前向引用规则。如果出现以下情况,则无法使用前向引用:

  • 用法发生在C的实例(分别是静态)变量初始化程序或C的实例(分别是静态)初始化程序中。
  • 用法不在作业的左侧。
  • 使用方法是一个简单的名称。
  • C是封闭用法的最里面的类或接口。

由于所有这些都适用于您的示例,因此前向引用是非法的。

答案 4 :(得分:1)

详细说明DFA的答案:

我认为绊倒你的是JLS 8.2.3.2中第二个要点中的“左手边”规则。在初始化中,myField位于左侧。在您的添加电话中,它位于右侧。这里的代码是隐含的:

boolean result = myField.add('test')  

您没有评估结果,但编译器仍然就像它在那里一样。这就是你的初始化在你的add()调用失败时通过的原因。

至于为什么这是如此,我不知道。我知道,这可能是为了方便JVM开发人员。

答案 5 :(得分:0)

我认为方法调用存在问题,因为如果没有add()的引用类型,编译器无法确定使用哪个myField方法。

在运行时,使用的方法将由对象类型确定,但编译器只知道引用类型。