Java常见疑难问题-类12
40. 构造器中静态常量的引用问题
class T {
// 先于静态常量t初始化,固可以在构造器中正常使用
private static final int y = getY();
/*
* 严格按照静态常量声明的先后顺来初始化:即t初始
* 化完后,才初始化后面的静态常量j,所以构造器中
* 引用后面的静态常量j时,会是0,即内存清零时的值
*/
public static final T t = new T();
// 后于静态常量t初始化,不能在构造器中正常使用
private static final int j = getJ();
private final int i;
static int getY() {
return 2;
}
static int getJ() {
return 2;
}
// 单例
private T() {
i = y – j – 1;
//为什么j不是2
System.out.println(“y=” + y + ” j=” + j);// y=2 j=0
}
public int getI() {
return i;
}
public static void main(String[] args) {
System.out.println(T.t.getI());// 1
System.out.println(T.j);// 2
}
}
该程序所遇到的问题是由类初始化顺序中的循环而引起的:初始化t时需调用构造函数,而
调用构造函数前需初始化所有静态成员,此时又包括对t的再次初始化。
T类的初始化是由虚拟机对main方法的调用而触发的。首先,其静态域被设置缺省值,其
中y、j被初始化为0,而t被初始化为null。接下来,静态域初始器按照其声明的顺序执行
赋值动作。第一个静态域是y,它的值是通过调用getY获取的,赋值操作完后结果为2。第
一个初始化完成后,再进行第二个静态域的赋值操作,第二个静态域为t,它的值是通过调
用T()构造函数来完成的。这个构造器会用二个涉及静态域y、j来初始化非静态域i。通常,
读取一个静态域是会引起一个类被初始化,但是我们又已经在初始化T类。JavaVM规范对
递归的初始化尝试会直接被忽略掉(按理来说在创建出实例前需初始化完所有的静态域后
再来创建实例),这样就导致在静态域被初始化之前就调用了构造器,后面的静态域j将得
不到正常的初始化前就在构造器中被使用了,使用时的值为内存分配清零时的,即0。
当t初始化完后,再初始化j,此时j得到的值为2,但此时对i的初始化过程来说已经晚了。
在final类型的静态域被初始化之前,存在着读取其值的可能,而此时该静态域包含的还只
是其所属类型的缺省值。这是与直觉想违背的,因为我们通常会将final类型的域看作是常
量,但final类型的域只有在其初始化表达式是字面常量表达式时才是真正的常量。
再看看另一程序:
class T {
private final static int i = getJ();
private final static int j;
static {
j = 2;
}
static int getJ() {
return j;
}
public static void main(String[] args) {
System.out.println(T.j);// 2
/*
* 因为上面的语句已经初使完T类,所以下面语句是
* 不 会 再引起类的初始化,这里的结果用的是第一
* 次( 即上面语句)的初始化结果
*/
System.out.println(T.i);// 0
}
}
为什么第二个输出是0而不是2呢?这就是因为VM是严格按照你声明的顺序来初始化静
态域的,所以前面的引用后面的静态域时,基本类型就是0,引用类型就会是null。
所以要记住:静态域,甚至是final类型的静态域,可能会在它们被初始化之前,被读走其
缺省值。
另,类初始化规则请参考《惰性初始化》一节
声明: 除非转自他站(如有侵权,请联系处理)外,本文采用 BY-NC-SA 协议进行授权 | 嗅谱网
转载请注明:转自《Java常见疑难问题-类12》
本文地址:http://www.xiupu.net/archives-245.html
关注公众号:
微信赞赏
支付宝赞赏