【Java笔记】类的初始化顺序

    读这篇文章之前,请读者先阅读笔者的一篇关于静态代码块和构造代码块的文章。

    笔者在学习Java的时候就老是产生一系列的疑问。这种学习方法是好的。学习任何东西,不能仅仅是被动的接受,还应该思考。在最初学习Java的时候,笔者就不禁好奇,虚拟机到底完成了哪些工作?虚拟机是怎么分配内存的?虚拟机是怎样执行字节码的?当然,想弄明白这些问题,就应该看一本《深入理解Java虚拟机》。

    在学习类的时候,读者又不禁想问:Java中的类是怎样在内存中运作的?Java的对象是怎样产生的?Java中的类是如何加载进入内存的?一开始运行程序就要将所有的类加载进内存么,否则又是怎样的呢?这系列的问题,困惑着笔者。没弄明白一个问题,笔者都会将它整理成笔记,以供大家阅读。

    在此贴出实例代码:

package lin;
import static java.lang.System.out;
public class Lin {

    { //这是构造代码块
        System.out.println("Normal block start.");
        t1 = new Test(1);
        t2 = new Test(2);
        t3 = new Test(3);
        System.out.println("Normal block end.");
    }

    static  //这是静态代码块
    {
        System.out.println("Static block start.");
        t = new Test(0);
        System.out.println("Static block end.");
    }
    public static void main(String args[]) {

        System.out.println("main() start.");
        new Lin();
        new Lin();
        System.out.println("main() end.");

    }
    public Lin() {
 
        System.out.println("Lin()");

    }
    public Test t1;
    public Test t2;
    public Test t3;
    public static Test t;

}
class Test {

    public int a = 0;
    Test() {

        System.out.println("Test");

    }
    Test(int a) {

        this.a = a;
        System.out.println("Test(" + a + ")");

    }

}
<span style="font-family:SimSun;">//执行结果
/*
Static block start.
Test(0)
Static block end.

main() start.

Normal block start.
Test(1)
Test(2)
Test(3)
Normal block end.
Lin()

Normal block start.
Test(1)
Test(2)
Test(3)
Normal block end.
Lin()

main() end.
*/
</span>
   

    运行结果显示:类的静态代码块 - main方法 - 构造代码块 - 构造方法 - 构造代码块 - 构造方法。实例化了两个对象,但静态代码块只执行了一次。

    下面我要介绍的就是Java虚拟机是怎样加载类的,以及类的初始化顺序。要想有汽车,必须要有图纸。因此先要有类,才能与对象。

    1. 类的加载。

    一个程序会有很多类,甚至一个大的项目会有成千上万各类。在运行程序的时候,将这些大杂烩全部扔到内存里面么?答案肯定是否定的。这些类中总有一个主类,即主函数入口所在的类。运行程序的时候,首先要将这个类加载进内存。有人不禁就会问了,为什么是类,而不是对象?那有没有注意到main方法都会有一个public和static来修饰呢?对,mian方法其实是属于类的,它不需要实例化类的对象便可直接调用。要调用一个陌生的类,Java虚拟机就会到CLASSPATH指定的路径去所搜这个类。当然类是在包中的。那么怎么加载呢?有这么几个步骤:

    装载:查找和导入类或接口的二进制数据;
    链接:执行下面的校验、准备和解析步骤,其中解析步骤是可以选择的;
    校验:检查导入类或接口的二进制数据的正确性;
    准备:给类的静态变量分配并初始化存储空间;
    解析:将符号引用转成直接引用;
    初始化:激活类的静态变量的初始化Java代码和静态Java代码块(初始化类中属性是静态代码块的常用用途,但只能使用一次)。

    注:在Java中,类装载器把一个类装入Java虚拟机中,要经过三个步骤来完成:装载、链接和初始化,其中链接又可以分成校验、准备和解析三步,除了解析外,其它步骤是严格按照顺序完成的。

    加载类,就会将类中的静态方法加载进入内存(堆中)。并且在堆中分配一块空间用来保存静态域,同时初始化它。如果某个域是某个类的对象,那么就要按照上面的步骤把那个类加载进入内存。怎样初始化类的静态域呢?那就是前面介绍的静态代码块了。这是在初始化类最先执行的。

    2. 构造对象。

    加载了类,就有了类的设计图纸,就可以实例化对象了。前面介绍过,在构造器调用之前的哪些工作虚拟机已经做好了。因此就要先完成这些工作。那就是要执行构造代码块了。完成这些工作之后,才调用类的构造器,让客户端程序员去初始化对象的域。

    综上:类先加载进入内存,并且先初始化类的静态域。然后实例化类的对象,并且初始化对象域。也就是说带static修饰的域先被初始化。他们的顺序就是:静态代码块 -> 构造代码块 -> 构造器。

    当然还有特殊的情形,那就是如果该类继承了某个超类。那这样的情形,初始化顺序又是什么样的呢?可想而知,没有父类哪来的子类?所以顺序自然是这样的:

    1.  父类静态成员和静态初始化块 ,按在代码中出现的顺序依次执行
    2.  子类静态成员和静态初始化块 ,按在代码中出现的顺序依次执行
    3.  父类实例成员和实例初始化块 ,按在代码中出现的顺序依次执行
    4.  父类构造方法
    5.  子类实例成员和实例初始化块 ,按在代码中出现的顺序依次执行
    6.  子类构造方法

    读者可以自己写一个小程序试验一下结果。



你可能感兴趣的:(java,类,对象,初始化顺序)