关于类初始化与实例初始化
当我们在创建一个实例对象时,这个对象的创建是按照怎样的规则被初始化的,或者说其各个属性及代码片段的初始化顺序是怎样的呢?这是面试的一个高频考点,也是作Java程序员的我们起码要掌握的知识。希望这篇文章在这方面能对你有所帮助。
程序分析
首先我们来分析下面的程序将会运行出怎样的结果:
正确答案是:
为什么会运行出这样的结果呢?
类初始化与实例初始化
当一个对象被创建时,jvm会先去判断该对象所属的类是否已被初始化,如果该类已被初始化,则直接进行该对象的初始化;如果该类未被初始化过,则会先去初始化该类,再去初始化该对象。
对于 Father son1 = new Son() 我们创建了一个Son的实例对象,首先会进行类Son的初始化,而后进行该实例的初始化。
而对于 Father son2 = new Son(),由于之前已经进行过Son类的初始化,本次创建实例,只需对实例进行初始化。
那么类初始化和实例初始化的过程是怎样的呢?
类初始化
- 一个类要创建实例,需要先加载并初始化该类。
如果该类已经创建过实例对象,则该类已经初始化过,将不会再次初始化
main() 方法所在的类需要先加载和初始化 - 一个子类要初始化需要先初始化父类
- 一个类的初始化就是执行其 clinit() 方法
① clinit() 方法由类变量显式赋值代码和静态代码块代码组成
② 类变量显式赋值代码和静态代码块代码从上到下顺序执行
③ clinit() 方法只执行一次(因为类只会被初始化一次)
实例初始化
- 实例初始化就是执行其 init() 方法
① 有几个构造器就有几个 init() 方法,所以 init() 方法可能重载有多个
② init() 方法由实例变量显式赋值代码和非静态代码块代码、对应构造器代码组成
③ 实例变量显式赋值代码和非静态代码块代码从上到下顺序执行
④ 每次创建实例对象,调用对应构造器,执行的就是对应的 init() 方法
⑤ init() 方法的首行是super() 或super(实参列表),即对应父类的init() 方法
每一个类都有 clinit() 和 init() 方法,即类初始化方法和实例初始化方法。它们并非由我们自己编写,而是编译器自动生成的。查看字节码文件可以找到这两个方法的执行记录
回归分析
知道以上几点后,再回到之前的程序中:
上述程序问题的分析过程如下:
对于 Father son1 = new Son():类初始化 + 实例初始化
- 父类的初始化 (clinit)
① j = method2() (5)
② 父类静态代码块 (2) - 子类的初始化 (clinit)
① j = method2() (10)
② 子类静态代码块 (7) - 子类实例初始化 (init)
① super() 父类实例初始化 (init) (最前)
⑴ super()
⑵ i = method1() (4)
⑶ 父类非静态代码块 (1)
⑷ 父类构造方法 (3)
② i = method1() (9)
③ 子类非静态代码块 (6)
④ 子类构造方法(最后) (8)
对于 Father son2 = new Son():已经进行过类初始化,所以本次创建实例对象仅进行实例初始化
- 子类实例初始化 (init)
① super() 父类实例初始化 (init) (最前)
⑴ super()
⑵ i = method1() (4)
⑶ 父类非静态代码块 (1)
⑷ 父类构造方法 (3)
② i = method1() (9)
③ 子类非静态代码块 (6)
④ 子类构造方法(最后) (8)
其中对于 3. 子类实例初始化 和 ① super :
super()是最先执行的,类构造器最后执行,其他实例变量显式赋值代码和非静态代码块代码按照从上到下的顺序依次执行。
接下来,我们再来看一个问题,分析下面这个程序的执行结果
另外
分析以下代码将会运行出怎样的结果
正确答案:
看到结果后与之前的结果进行对比,不难发现第一个 (4) 变成了 (9)。其实不难理解,按照我们之前的问题分析,第 3.①.⑵ 步中的 i = method1() 方法,被子类Son重写了,所以会执行子类重写的method1()方法。那为什么父类method2执行的结果是 ⑸ 呢?由于类总是先于对象被初始化,而且静态属性与静态方法与对象无关,只与类本身有关,所以父类在初始化 j = method2 时,执行的是自己的方法,这也是为什么静态方法无法被重写的原因。
方法的覆盖重写 Override
① 哪些方法不可以重写
- final 方法
- static 方法
- private 方法
② 对象的多态性
- 子类如果重写了父类的方法,那么通过子类对象调用的方法一定是子类重写过的
- 非静态方法默认的调用对象是 this
- this对象在构造器或者说是在 init() 方法中就是正在创建的对象