读这篇文章之前,请读者先阅读笔者的一篇关于静态代码块和构造代码块的文章。
笔者在学习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虚拟机是怎样加载类的,以及类的初始化顺序。要想有汽车,必须要有图纸。因此先要有类,才能与对象。
注:在Java中,类装载器把一个类装入Java虚拟机中,要经过三个步骤来完成:装载、链接和初始化,其中链接又可以分成校验、准备和解析三步,除了解析外,其它步骤是严格按照顺序完成的。
加载类,就会将类中的静态方法加载进入内存(堆中)。并且在堆中分配一块空间用来保存静态域,同时初始化它。如果某个域是某个类的对象,那么就要按照上面的步骤把那个类加载进入内存。怎样初始化类的静态域呢?那就是前面介绍的静态代码块了。这是在初始化类最先执行的。
2. 构造对象。
加载了类,就有了类的设计图纸,就可以实例化对象了。前面介绍过,在构造器调用之前的哪些工作虚拟机已经做好了。因此就要先完成这些工作。那就是要执行构造代码块了。完成这些工作之后,才调用类的构造器,让客户端程序员去初始化对象的域。
综上:类先加载进入内存,并且先初始化类的静态域。然后实例化类的对象,并且初始化对象域。也就是说带static修饰的域先被初始化。他们的顺序就是:静态代码块 -> 构造代码块 -> 构造器。
当然还有特殊的情形,那就是如果该类继承了某个超类。那这样的情形,初始化顺序又是什么样的呢?可想而知,没有父类哪来的子类?所以顺序自然是这样的:
1. 父类静态成员和静态初始化块 ,按在代码中出现的顺序依次执行
2. 子类静态成员和静态初始化块 ,按在代码中出现的顺序依次执行
3. 父类实例成员和实例初始化块 ,按在代码中出现的顺序依次执行
4. 父类构造方法
5. 子类实例成员和实例初始化块 ,按在代码中出现的顺序依次执行
6. 子类构造方法
读者可以自己写一个小程序试验一下结果。