java同其他语言不同,在类首次使用时,类的class字节码才会加载到内存中,通过加载、连接、初始化这三个步骤来对该类进行初始化。
- 加载:是指将类的class文件读入内存,并为之创建一个该类的java.lang.Class对象(注意并不是目标类的对象)。也就是说当程序中使用任何类时都会为之创建一个java.lang.Class对象。
- 连接:类的连接又可以分为如下三个阶段:
- 验证:验证阶段用于检验被加载的类是否有正确的内部结构,并和其他类协调一致;
- 准备:类的准备阶段则负责为类的静态属性分配内存,并设置默认初始值;
- 解析:将类的二进制数据中的符号引用替换成直接引用。
- 初始化:对变量进行初始化。初始化顺序如下:
- 父类静态成员和静态初始化快,按定义顺序执行。
- 子类静态成员和静态初始化块,按定义顺序执行。
- 父类的实例成员和实例初始化块,按定义顺序执行。
- 执行父类的构造方法。
- 子类实例成员和实例初始化块,按定义顺序执行。
- 执行子类的构造方法。
java类的代码只在类第一次使用时才加载,包括创建第一个对象和第一次调用static。static块只在第一次初始化时初始化,并且只初始化一次。(ClassLoader.getSystemClassLoader().loadClass这个方式只加载类,没有初始化操作,所以static也不会在这个时候初始化)。
关于初始化,使用.class和ClassLoader.getSystemClassLoader().loadClass方式获得类的引用,是不会进行初始化的,并且如果是“编译期常量”,是不需要初始化就能使用的。
简单例子:
public class Test {
public static Random rand = new Random(53);
public static void main(String[] args) {
Class c = T1.class;
System.out.println("after T1 ref");
System.out.println(T1.a);
System.out.println(T1.b);
System.out.println(T2.a);
try {
Class cc = Class.forName("ttt.T3");
System.out.println("after T3 ref");
System.out.println(T3.a);
Class ccc = ClassLoader.getSystemClassLoader().loadClass("ttt.T4");
System.out.println("after T4 ref");
System.out.println(T4.a);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
class T1 {
static final int a = 23;
static final int b = Test.rand.nextInt(12);
static {
System.out.println("-----T1-----");
}
}
class T2 {
static int a = 123;
static {
System.out.println("-----T2-----");
}
}
class T3 {
static int a = 232;
static {
System.out.println("-----T3-----");
}
}
class T4 {
static int a = 235;
static {
System.out.println("-----T4-----");
}
}
运行结果:
after T1 ref
23
-----T1-----
8
-----T2-----
123
-----T3-----
after T3 ref
232
after T4 ref
-----T4-----
235
上述例子就很好体现了.class和ClassLoader.getSystemClassLoader().loadClass获得类的引用时是不会进行初始化的,并且static final 常量是可以直接使用的。
下面再用简单的例子详细描述下整个过程:
public class Test {
private static Test test = new Test(5);
public static int a;
public static int b = 3;
public int c = getI();
public int d = 2;
public String aa;
static {
System.out.println("static==" + a);
System.out.println("static==" + b);
}
{
System.out.println("non static==" + a);
System.out.println("non static==" + b);
System.out.println("non static==" + c);
System.out.println("non static d==" + d);
}
public int getI() {
System.out.println("======getI======="+d);
d++;
System.out.println("======getI======="+d);
return d;
}
public Test(int i) {
System.out.println("===========i==========" + i);
System.out.println("==" + test);
System.out.println("==" + a);
System.out.println("==" + b);
System.out.println("==" + c);
System.out.println("==" + d);
a++;
b++;
c++;
d++;
System.out.println("===" + a);
System.out.println("===" + b);
System.out.println("===" + c);
System.out.println("===" + d);
}
// public static Test getIns() {
// return test;
// }
public static void main(String[] args) {
// Test test = Test.getIns();
Test test = new Test(15);
System.out.println("---------------------------");
System.out.println(test.a);
System.out.println(test.b);
System.out.println(test.c);
System.out.println(test.d);
}
}
首先,寻找main方法,找到后由于是首次调用Test这个类,所以Test的class字节码会被载入内存,创建Class对象。
接着,是连接的三个阶段,其中准备阶段会将类的静态属性分配内存,并设置默认初始值。(所有的基本类型会赋予其默认值,对象引用会赋予null)。
然后,进行初始化操作。由于是按代码顺序执行,当执行第一句
private static Test test = new Test(5);
由于遇到new关键字,需要创建对象(创建对象调用类的构造器,实际上构造器是隐式的static),又由于static只在首次时进行初始化操作,并且只执行一次,所以这次不会在执行static,所以直接对实例变量进行赋默认值并初始化,然后调用构造方法。接着,顺序执行之前未完成的static的初始化操作,然后是main方法中遇到new关键字,执行上述过程。最后,执行main方法里的其他语句。
程序运行结果:
======getI=======0 //在打印这部之前,已经为static分配了内存,并赋予默认值,遇到第一句初始化,所以进行实例变量的初始化操作
======getI=======1 //程序走到这里d已经变为1
non static==0 //执行非静态块(同非静态变量一起按顺序执行)
non static==0
non static==1 //由于执行了getI方法,c被赋值为d的值
non static d==2 //对d进行显式初始化,所以d的值变为2了,这也说明实例变量在分配内存后也会被赋予默认值
===========i==========5 //执行构造函数(这里是第一句的new关键字创建的对象)
==null //由于对象未创建,对象引用被赋予null,所以这里是null
==0
==0 //static 变量还未初始化,只赋予默认值,所以是0
==1 //由于创建对象需要先初始化实例变量,所以这里已经初始化为1了
==2 //由于创建对象需要先初始化实例变量,所以这里已经初始化为2了(注意,这2个变量都是由于第一句static初始化时需要创建对象而进行的初始化,并非说明实例变量先于static变量初始化)
===1 //对4个变量进行增量操作了
===1
===2
===3
static==1 //static按顺序执行,所以在第一句static初始化创建了Test对象后,又回到static的初始化了
static==3 //之前b的值为1,但初始化为3了,所以这里打印出来是3
======getI=======0 //static初始化完成了,程序执行main方法里的代码,第一句又遇到new关键字创建对象,重复上述过程了,只不过这次是第二次调用Test类,所以不需要初始化static了
======getI=======1
non static==1
non static==3
non static==1
non static d==2
===========i==========15
==com.busi.controller.web.member.Test@15db9742 //这里已经在static初始化的时候创建了对象了,所以不为null了
==1
==3
==1
==2
===2
===4
===2
===3
--------------------------- //执行main方法中其余代码
2
4
2
3
以上是单个类的初始化过程,有继承的,需要先进行父类的初始化,过程与此相同,需要注意的是创建子类对象时,会先调用父类的无参构造函数,如果父类的无参构造函数中调用了被子类重写的方法,则调用子类重写过的方法,子类中可以通过super来调用父类被重写的方法。
本文参考:http://www.cnblogs.com/amunote/p/4170627.html