Java面试中经常考查同学们的一个问题就是对象的初始化顺序。本文就重点说一说Java中的类和对象的初始化顺序。
初始化顺序
我们都知道,在类的内部,变量定义的先后顺序决定了初始化的顺序。即使变量定义散布于方法定义之间,它们仍旧会在任何方法(包括构造器)被调用之前得到初始化。看看下面这个例子👇:
1 | class Window { |
输出结果如下:
1 | Window(1) |
从输出的结果大家可以注意到这样一个事实,就是对象w3
被初始化了两次。在House
类中,故意将几个Window
对象的定义散布到各处,以证明它们全部都会在调用构造器或者其他方法之前得到初始化。另外,我们也知道对象w3
后来又在构造器内被初始化了。
静态数据的初始化
无论创建多少个对象,静态数据都只占用一份存储区域。static
关键字不能应用于局部变量,因此它只能作用于域。如果一个域是静态的基本类型,并且没有对它进行初始化,那么它就会获得基本类型的标准初值;如果它是一个对象引用,那么它的默认初始化值就是null
。看看下面这个例子:
1 | public class LoadingOrder { |
输出结果如下:
1 | Child(1) |
为什么会是这个结果呢?我们可以看到,在Parent
类中,我们按照先后顺序定义了age
,child1
,child3
,child2
等域。其中child3
是非静态域。输出的结果是先输出了Child(1)
,然后是Child(2)
,接着是Child(3)
。注意这儿,这里说明了一个事实,就是静态域的初始化早于非静态域。然后输出Parent(13)
,说明非静态域的初始化又早于构造函数。最后一个输出0
印证了我们前面说的基本数据类型会取相关的初值。
总结一下对象创建的过程,假设有一个类叫People
类:
- 即使没有显示地使用
static
关键字,构造器实际上也是静态方法。因此,当首次创建类型为People
的对象时(构造器可以看成是静态方法),或者People
类的静态方法/静态域首次被访问时,Java解释器必须查找类路径,以定位People.class文件。 - 然后载入People.class,这将会在堆中创建一个
Class
对象,有关静态初始化的所有动作都会被执行。因此,静态初始化只有在Class对象首次加载的时候才会执行一次。 - 当使用
new People()
创建对象的时候,首先在堆上为People
对象分配足够的存储空间,关于存储空间到底多大,这是根据People
类中的数据结构决定的。 - 这块存储空间会被清零,这就自动地将
People
对象中的所有基本类型的数据都设置成了默认值(对于数值型的就是0,布尔型为false
(底层是0),字符型为0),而引用则被设置为了null
。 - 执行所有出现于字段定义出的初始化动作。
- 执行构造器。后面与继承结合起来的时候情况更加复杂。
子类父类静态块初始化顺序
关于这部分内容,我建议大家移步到我的另一篇博文。
static静态代码块,静态变量等加载顺序研究