关闭

java类加载和初始化顺序

107人阅读 评论(0) 收藏 举报
分类:

java同其他语言不同,在类首次使用时,类的class字节码才会加载到内存中,通过加载、连接、初始化这三个步骤来对该类进行初始化。

  • 加载:是指将类的class文件读入内存,并为之创建一个该类的java.lang.Class对象(注意并不是目标类的对象)。也就是说当程序中使用任何类时都会为之创建一个java.lang.Class对象。
  • 连接:类的连接又可以分为如下三个阶段:
    1. 验证:验证阶段用于检验被加载的类是否有正确的内部结构,并和其他类协调一致;
    2. 准备:类的准备阶段则负责为类的静态属性分配内存,并设置默认初始值;
    3. 解析:将类的二进制数据中的符号引用替换成直接引用。
  • 初始化:对变量进行初始化。初始化顺序如下:
    1. 父类静态成员和静态初始化快,按定义顺序执行。
    2. 子类静态成员和静态初始化块,按定义顺序执行
    3. 父类的实例成员和实例初始化块,按定义顺序执行
    4. 执行父类的构造方法。
    5. 子类实例成员和实例初始化块,按定义顺序执行
    6. 执行子类的构造方法。
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

0
0

查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    个人资料
    • 访问:363次
    • 积分:61
    • 等级:
    • 排名:千里之外
    • 原创:2篇
    • 转载:0篇
    • 译文:0篇
    • 评论:0条
    文章分类
    文章存档