java类加载和初始化顺序

来源:互联网 发布:启信宝的数据可信度 编辑:程序博客网 时间:2024/06/04 19:28

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 ref23-----T1-----8-----T2-----123-----T3-----after T3 ref232after 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已经变为1non 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===3static==1            //static按顺序执行,所以在第一句static初始化创建了Test对象后,又回到static的初始化了static==3            //之前b的值为1,但初始化为3了,所以这里打印出来是3======getI=======0   //static初始化完成了,程序执行main方法里的代码,第一句又遇到new关键字创建对象,重复上述过程了,只不过这次是第二次调用Test类,所以不需要初始化static了======getI=======1non static==1non static==3non static==1non static d==2===========i==========15==com.busi.controller.web.member.Test@15db9742 //这里已经在static初始化的时候创建了对象了,所以不为null了==1==3==1==2===2===4===2===3--------------------------- //执行main方法中其余代码2423

以上是单个类的初始化过程,有继承的,需要先进行父类的初始化,过程与此相同,需要注意的是创建子类对象时,会先调用父类的无参构造函数,如果父类的无参构造函数中调用了被子类重写的方法,则调用子类重写过的方法,子类中可以通过super来调用父类被重写的方法。

本文参考:http://www.cnblogs.com/amunote/p/4170627.html

0 0