关于类加载的时机与过程
思考以下代码的输出结果:
public class Singleton {
private static Singleton instance = new Singleton();
public static int count1;
public static int count2 = 0;
private Singleton() {
count1 ++;
count2 ++;
}
public static Singleton getInstance() {
return instance;
}
}
public class Test {
public static void main(String[] args) {
Singleton singleton = Singleton.getInstance();
System.out.println("count1 = " + Singleton.count1);
System.out.println("count2 = " + Singleton.count2);
}
}
错误答案
count1 = 1
count2 = 1
正确答案
count1 = 1
count2 = 0
这个问题就是牵涉到类的加载与过程,虚拟机定义了以下六种情况,如果类未被初始化,则会进行初始化:
1、创建类的实例
2、访问类的静态变量(除常量【被final修辞的静态变量】原因:常量一种特殊的变量,因为编译器把他们当作值(value)而不是域(field)来对待。如果你的代码中用到了常变量(constant variable),编译器并不会生成字节码来从对象中载入域的值,而是直接把这个值插入到字节码中。这是一种很有用的优化,但是如果你需要改变final域的值那么每一块用到那个域的代码都需要重新编译。
3、访问类的静态方法
4、反射如(Class.forName("my.xyz.Test"))
5、当初始化一个类时,发现其父类还未初始化,则先出发父类的初始化
6、虚拟机启动时,定义了main()方法的那个类先初始化
我们来分析以下上述代码的执行情况:
- main()方法 Test类初始化
- main()方法第一句:访问Singleton的getInstance()静态方法 Singleton类初始化,此时按照代码执行顺序进行静态成员的初始化默认值
instance = null
count1 = 0
count2 = 0 - 按照代码执行顺序为类的静态成员赋值:
private static Singleton instance = new Singleton(); instance调用Singleton的构造方法,调用构造方法后 count1 = 1,count2 = 1
public static int count1; count1没有进行赋值操作,所以count1 = 1
public static int count2 = 0; count2进行赋值操作,所以count2 = 0 - main()方法第二句:访问Singleton的count1变量,由于count1没有赋初始值,所以count1 = 1
- main()方法第三句:访问Singleton的count2变量,由于count2赋了初始值 0,所以count2 = 0
如果我们把Singleton代码执行顺序变化一下:
public class Singleton {
public static int count1;
public static int count2 = 0;
private static Singleton instance = new Singleton();
private Singleton() {
count1++;
count2++;
}
public static Singleton getInstance() {
return instance;
}
}
此时输出结果就为:
count1 = 1
count2 = 1
如果改为如下代码,那么运行情况又是怎样:
public class Singleton {
Singleton(){
System.out.println("Singleton construct");
}
static {
System.out.println("Singleton static block");
}
public static final int COUNT = 1;
}
public class Test {
public static void main(String[] args) {
System.out.println("count = " + Singleton.COUNT);
}
}
运行结果为:
count = 1
由于常量在编译阶段会存入相应类的常量池当中,所以在实际调用中Singleton.COUNT并没有直接引用到Singleton类,因此不会进行Singleton类的初始化,所以输出结果为 count = 1