奇怪的Java行为与静态和最终限定符

在我们的团队中,我们发现了一些奇怪的行为,我们使用了staticfinal限定符。 这是我们的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值,因为静态类成员的代码是从上到下执行。

任何人都可以解释为什么这种行为发生?

这些是您运行程序时采取的步骤:

  1. main可以运行之前,必须按照外观顺序运行静态初始化程序来初始化Test类。
  2. 要初始化me领域,开始执行new Test()
  3. 打印I的值。 由于字段types是Integer ,看起来像编译时常量4变成了一个计算值( Integer.valueOf(4) )。 这个字段的初始值尚未运行,打印初始值为null
  4. 打印S的值。 由于它是用一个编译时常量初始化的,所以这个值被烘焙到参考站点中,打印abc
  5. new Test()完成,现在I执行的初始化器。

课程:如果您依赖于初始化的静态单例,将单例声明作为最后一个静态声明,或者使用静态初始化块,在所有其他静态声明之后。 这将使该类显示完全初始化为单身人士的build筑代码。

S是一个编译时常量,遵循JLS 15.28的规则。 因此,代码中出现的S被编译时已知的值replace。

如果您将I的types更改为int ,那么您也会看到相同的结果。

由于Integer数据types,你有奇怪的行为。 关于JLS 12.4.2静态字段是按照您编写的顺序初始化的,但是编译时常量是先初始化的。

如果不使用包装typesInteger而是inttypes,则会得到所需的行为。

你的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" 。 如果你为meI交换声明顺序,你会得到预期的结果,因为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"属性,这意味着它们的值可以通过查看类的常量池来解决,而不需要运行任何代码。