【java天梯-2】类的初始化顺序
概述
1 | Object o = new Object() |
上述代码为使用new
操作创建一个Object
类型的对象,在该对象从无到有的过程中,到底经历了哪些过程?按照什么顺序执行的?
属性
类中定义的属性分为静态和非静态两种。
其中静态属性与类对象无关,只与类有关,即只与Class有关,一经初始化在该类的所有对象中仅此一份。
而非静态属性需要依赖于该类的对象,即该类的每一个对象都持该属性,每个对象的该属性独立于其他对象存在。
类中仅定义但未手动提供默认值的属性(eg: private String username;
)会在类加载后被赋予默认值,何为默认值呢?
- 类属性为引用对象类型时,初始化默认值为null。包装类型和String类型也在其中
- 类属性为基本类型中的数值类型时,初始化默认值为0
- 雷属性为基本类型中的boolear类型时,初始化默认值为false
另外一种定义且手动提供默认值的属性(eg:private String username = “admin”;
)会直接被赋予手动指定的初值。
构造函数
构造函数在执行 new Object()
时被调用,在构造函数内可以调用父类构造函数、对属性赋值、执行自定义类的初始化操作
构造块
构造块分为静态和非静态两种,其功能均为完成一组初始化操作。静态构造块在非静态构造块之前执行,与出现的顺序无关。eg:
1 | { |
Code
了解了类初始化过程中的一些角色之后,看一段代码,分析一下类初始化中各个模块的执行顺序是怎样的。
1 | public class Test { |
当执行Test类的main方法运行该程序时,在main方法体内并没有输出语句,那为何会有这样的输出结果呢?其实在new一个对象的时候,只把构造函数作为类初始化的唯一要素是不够的,因为在执行构造函数之前,类的初始化小组已经干了许多活了。
什么?执行构造函数之前还有操作?做了啥事儿?
让我们在main方法体第一行添加一个输出语句 System.out.println("main函数开始执行");
,输出结果为:
1 | 使用 n= 5 初始化类A |
很显然,这段代码在main方法中new对象之前,确实做了不少工作。让我们分析一下该操作的执行顺序。
- 首先JVM加载Test类,按代码顺序初始化该类中所有的静态角色(静态属性、静态函数和静态构造块),即先执行Test类中
static A a2 = new A(2);
以2为构造函数参数构造类A对象。 - 转到类A,在执行类A构造函数
A(Integer n)
之前,类A被加载到JVM中,按代码顺序初始化该类中所有静态角色,即执行static B b1 = new B();
,使用类B的无参构造方法构造类B对象。 - 转到类B,在执行类B构造函数
B()
之前,类B被加载到JVM中,按代码顺序初始化该类中所有静态角色,现在类B中无静态角色,此时执行类属性初始化操作,即执行private A a5 = new A(5);
以5为构造函数参数构造类A对象 - 转到类A,因第2步中类A已经被JVM加载,静态属性已经进入初始化队列中,无需再次初始化,所以直接转到初始化类A的非静态属性,即
private Integer n = 0;
- 接着,执行类A带有一个Integer参数的构造函数,将5传入,输出
使用 n= 5 初始化类A
,并将n赋值为5 - 此时第三步中类B的属性初始化执行完毕,回到类B,执行类B的无参构造方法。在该构造函数内部,再次调用类A的无参构造函数构造类A的对象
- 转到类A,因类A的静态属性已经初始化完毕,但是其非静态属性需要依赖于类对象,所以执行类A非静态属性的初始化,n=0,输出
默认初始化类A, n= 0
,重新赋值给B的属性a5。 - 此时第2步中,真正去执行类A的构造函数
A(Integer n)
,传入参数2,构造类A对象。输出使用 n= 2 初始化类A
。至此,Test类第一个静态角色才被执行完毕 - 转到Test类,执行第二个静态角色,静态构造块,输出
Test 类的静态构造块被执行
。此时Test类所有静态角色被执行完毕 - 执行main函数,此时main函数才真正被执行。输出
main函数开始执行
,并执行Test test = new Test();
- 在执行
Test test = new Test();
之前,先检查该类静态角色是否已经初始化完毕,当然已经被初始化,那么接着按顺序执行非静态角色,即先执行private A a = new A();
构造类A对象 - 转到类A,因类A静态角色均初始化完毕,所以执行类A非静态角色初始化,即属性n被赋予初始值0,接着执行类A无参构造方法,输出
默认初始化类A, n= 0
- 转到Test,执行非静态构造块,输出
Test 类的非静态构造块被执行
- 执行Test类默认构造函数,完成Test类对象的最终构造
再执行main函数时,经过上述15步后,最终的Test类对象test才被new出来。可想而知,Java在new一个对象之前为我们执行了如此之多的初始化动作,所以了解Java对象的初始化确实可以在我们编写程序和解决Bug时带来便利。
类对象初始化顺序
经过上述的例子,总结一下类对象初始化时通过什么顺序,执行了哪些操作。假设有个名为Dog的类
- 及时没有显示的使用static关键字,构造器实际上也是静态方法。因此,当首次创建类型为Dog的对象时(构造器可以看成静态方法),或者Dog类的静态方法/静态域首次被访问时,Java解释器必须查找类路径,以定位Dog.class文件。
- 然后载入Dog.class,这将创建一个Class对象。因此静态初始化只在Class对象首次加载的时候进行。即此时执行静态初始化
- 当用new Dog()创建对象的时候,首先将在堆上为Dog对象分配足够的存储空间。
- 这块内存空间会被清零,这就自动的将Dog对象中的所有基本类型数据都设置成了默认值(对数字来说就是0,对布尔类型和字符类型也相同),而引用则被设置成了null
- 执行所有出现于字段定义处的初始化动作,非静态构造块等
- 执行构造器。
总结
通过对Java类初始化的学习,进一步了解了对象从无到有的过程,也对Java对象生命周期的前半部分做了一定的了解。
参考
- 《Java编程思想》 — 初始化与清理