奇怪的Java行为与静态和最终限定符
在我们的团队中,我们发现了一些奇怪的行为,我们使用了static
和final
限定符。 这是我们的testing课:
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
值,因为静态类成员的代码是从上到下执行。
任何人都可以解释为什么这种行为发生?
这些是您运行程序时采取的步骤:
- 在
main
可以运行之前,必须按照外观顺序运行静态初始化程序来初始化Test
类。 - 要初始化
me
领域,开始执行new Test()
。 - 打印
I
的值。 由于字段types是Integer
,看起来像编译时常量4
变成了一个计算值(Integer.valueOf(4)
)。 这个字段的初始值尚未运行,打印初始值为null
。 - 打印
S
的值。 由于它是用一个编译时常量初始化的,所以这个值被烘焙到参考站点中,打印abc
。 -
new Test()
完成,现在I
执行的初始化器。
课程:如果您依赖于初始化的静态单例,将单例声明作为最后一个静态声明,或者使用静态初始化块,在所有其他静态声明之后。 这将使该类显示完全初始化为单身人士的build筑代码。
S
是一个编译时常量,遵循JLS 15.28的规则。 因此,代码中出现的S
被编译时已知的值replace。
如果您将I
的types更改为int
,那么您也会看到相同的结果。
由于Integer
数据types,你有奇怪的行为。 关于JLS 12.4.2静态字段是按照您编写的顺序初始化的,但是编译时常量是先初始化的。
如果不使用包装typesInteger
而是int
types,则会得到所需的行为。
你的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"
。 如果你为me
和I
交换声明顺序,你会得到预期的结果,因为I
将在构造函数被调用之前被初始化。 您也可以将I
的types从Integer
更改为int
。
因为4
需要自动装箱(即包装在一个Integer
对象中),所以它不是一个编译时常量,并且是静态初始化块的一部分。 但是,如果types为int
,则数字4
将是编译时常量,因此不需要显式初始化。 因为"abc"
是一个编译时常量, S
的值按预期打印。
如果你要更换,
public static final String S = "abc";
用,
public static final String S = new String("abc");
那么你会注意到S
的输出也是"null"
。 为什么会发生? 出于同样的原因, I
也输出"null"
。 类似这些字段,常量值(不需要自动装箱,如String
)的字段在编译时被赋予"ConstantValue"
属性,这意味着它们的值可以通过查看类的常量池来解决,而不需要运行任何代码。