这个问题在这里已有答案:
在我们的团队中,我们发现了一些奇怪的行为,我们使用了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
值,因为静态类成员的代码是从上到下执行的。
任何人都可以解释为什么会发生这种行为?
以下是运行程序时采取的步骤:
main
之前,必须通过按外观顺序运行静态初始化程序来初始化Test
类。me
字段,请开始执行new Test()
。I
的值。由于字段类型是Integer
,看起来像编译时常量4
变成计算值(Integer.valueOf(4)
)。该字段的初始化程序尚未运行,打印初始值null
。S
的值。由于它是使用编译时常量初始化的,因此将该值烘焙到引用站点中,打印abc
。new Test()
完成,现在I
的初始化程序执行。课程:如果您依赖于急切初始化的静态单例,请将单例声明放在最后一个静态字段声明中,或者使用在所有其他静态声明之后发生的静态初始化程序块。这将使类完全初始化为单例的构造代码。
S
是一个编译时常量,遵循JLS 15.28的规则。因此,代码中任何出现的S
都将替换为编译时已知的值。
如果你将I
的类型更改为int
,你也会看到同样的情况。
由于Integer
数据类型,您有奇怪的行为。关于JLS 12.4.2静态字段按您编写的顺序初始化,但首先初始化编译时常量。
如果您不使用包装类型Integer
但使用int
类型,则可以获得所需的行为。
你的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"
属性归因,这意味着只需查看类的常量池就可以解析它们的值,而无需运行任何代码。