在我们的团队中,我们发现了一些奇怪的行为,我们使用了static
和final
限定符。这是我们的测试类:
public class Test {
public static final Test me = new Test();
public static final Integer I = 4;
public static final String S = "abc";
public Test() {
System.out.println(I);
System.out.println(S);
}
public static Test getInstance() { return me; }
public static void main(String[] args) {
Test.getInstance();
}
}
当我们运行main
方法时,我们得到以下结果:
null
abc
我会理解它是否同时写入null
值,因为静态类成员的代码是从上到下执行的。
任何人都可以解释为什么会发生这种情况吗?
答案 0 :(得分:109)
这些是您运行程序时采取的步骤:
main
之前,必须通过按照外观顺序运行静态初始化程序来初始化Test
类。me
字段,请开始执行new Test()
。I
的值。由于字段类型为Integer
,因此看起来像编译时常量4
的变为计算值(Integer.valueOf(4)
)。此字段的初始化程序尚未运行,打印初始值null
。S
的值。由于它是使用编译时常量初始化的,因此该值将被引入引用站点,并打印abc
。new Test()
完成,现在I
的初始化程序已执行。课程:如果您依赖于急切初始化的静态单例,请将单例声明放在最后一个静态字段声明中,或者使用在所有其他静态声明之后发生的静态初始化程序块。这将使类完全初始化为单例的构造代码。
答案 1 :(得分:71)
S
是一个编译时常量,遵循JLS 15.28的规则。因此,代码中S
的任何出现都将替换为编译时已知的值。
如果您将I
的类型更改为int
,您也会看到相同的内容。
答案 2 :(得分:21)
由于Integer
数据类型,您的行为很奇怪。关于JLS 12.4.2静态字段按您编写的顺序初始化,但首先初始化编译时常量。
如果您不使用包装类型Integer
但使用int
类型,则可以获得所需的行为。
答案 3 :(得分:14)
您的Test
汇编成:
public class Test {
public static final Test me;
public static final Integer I;
public static final String S = "abc";
static {
me = new Test();
I = Integer.valueOf(4);
}
public Test() {
System.out.println(I);
System.out.println("abc");
}
public static Test getInstance() { return me; }
public static void main(String[] args) {
Test.getInstance();
}
}
如您所见,Test
的构造函数在初始化I
之前被调用。这就是为"null"
打印I
的原因。如果要交换me
和I
的声明顺序,则会得到预期的结果,因为在调用构造函数之前将初始化I
。您还可以将I
的类型从Integer
更改为int
。
因为4
需要进行自动装箱(即包装在Integer
对象中),所以它不是编译时常量,而是静态初始化程序块的一部分。但是,如果类型为int
,则数字4
将是编译时常量,因此不需要显式初始化。由于"abc"
是编译时常量,因此S
的值将按预期打印。
如果你要替换,
public static final String S = "abc";
用,
public static final String S = new String("abc");
然后您会注意到S
的输出也是"null"
。为什么会这样?出于同样的原因,I
也会输出"null"
。这些具有文字,常量值(不需要自动装箱,如String
)的字段在编译时会被"ConstantValue"
属性归因,这意味着它们的值可以是只需通过查看课程即可解决问题。常量池,无需运行任何代码。