写在前面的话:
一个培训出来的朋友找工作,遇到了关于"类的实例化创建过程"的笔试题,一脸懵逼的问我,一番了解后,发现培训学校只会发笔试题,并不会详细讲解类的实例化过程(并非diss培训机构,估摸着是这货没认真听),故有此文,给自己,也给所有初学java的朋友们。
本文将从 概念 + 例题(若干) 的形式,讲解类的实例化过程。
——万丈高楼平地起,勿以浮沙筑高台。
一、概念篇1、关于static:
答:书上说,static指“静态的”。可理解为“全局的”,如:全局的属性,全局的方法,全局的代码块。
1)全局的属性、全局的方法:即这个类所有的对象都共有的属性和方法。因为类是共有的,所有可声明直接调用。因此,也可理解为“单例模式”的属性和方法。何为单例模式?这个类声明的所有对象都共享这些属性和方法,一个对象对这个属性做了修改,那么所有的对象再次调用这个属性,就都是修改后的值了。
2)全局的代码块:随着类的加载而执行,且只执行一次。
2、类的实例化过程:
答:1)给对象分配空间,对属性按类型进行默认初始化。(8种基本数据类型,均按照默认方式初始化,其他数据类型默认为null)
2)加载父类静态代码块
3)加载本类静态代码块
4)加载子类静态代码块
5)初始化父类属性
6)初始化父类构造代码块
7)初始化父类构造方法
8)初始化本类属性。
9)初始化本类构造代码块
10)初始化本类构造方法
11)初始化子类属性
12)初始化子类构造代码块
13)初始化子类构造方法
以上可总结为3点:对象空间的分配,属性默认初始化 ---> 静态代码块初始化 ---> 属性、构造代码块、构造方法初始化(哪个写在前面先初始化哪个)。
二、例题篇
1、爷 --> 父 --> 子:
1)代码如下,要求列出结果:
/**************************************************** * * 爷类 * * * @author Francis * @date 2019/8/21 13:57 * @version 1.0 **************************************************/ public class GrandParent { int num = 25; static { System.out.println("爷类静态代码块"); System.out.println(); } { System.out.println("爷类构造代码块1," + num); num = 21; System.out.println("爷类构造代码块2," + num); doSomething(); System.out.println(); } GrandParent() { System.out.println("爷类构造方法1," + num); num = 32; System.out.println("爷类构造方法2," + num); doSomething(); System.out.println(); } void doSomething() { System.out.println("爷类doSomething方法1," + num); num = 33; System.out.println("爷类doSomething方法2," + num); System.out.println(); } }
/**************************************************** * * 父类 * * * @author Francis * @date 2019/8/21 13:46 * @version 1.0 **************************************************/ public class Parent extends GrandParent{ int num = 5; static { System.out.println("父类静态代码块"); System.out.println(); } { System.out.println("父类构造代码块1," + num); num = 1; System.out.println("父类构造代码块2," + num); doSomething(); System.out.println(); } Parent() { System.out.println("父类构造方法1," + num); num = 2; System.out.println("父类构造方法2," + num); doSomething(); System.out.println(); } void doSomething() { System.out.println("父类doSomething方法1," + num); num = 3; System.out.println("父类doSomething方法2," + num); System.out.println(); } }
/**************************************************** * * 子类 * * * @author Francis * @date 2019/8/21 13:47 * @version 1.0 **************************************************/ public class Child extends Parent{ int num = 10; /* 静态代码块,在类加载时执行 */ static { System.out.println("子类静态代码块"); System.out.println(); } /* 构造代码块 */ { System.out.println("子类构造代码块1," + num); num = 11; System.out.println("子类构造代码块2," + num); doSomething(); System.out.println(); } Child() { System.out.println("子类构造方法1," + num); num = 12; System.out.println("子类构造方法2," + num); doSomething(); System.out.println(); } void doSomething() { System.out.println("子类doSomething方法1," + num); num = 13; System.out.println("子类doSomething方法2," + num); System.out.println(); } }
/**************************************************** * * 测试类的实例化过程 * * * @author Francis * @date 2019/8/21 13:49 * @version 1.0 **************************************************/ public class TempTest extends DemoApplicationTests{ @Test public void method1(){ Child child = new Child(); child.num = 20; child.doSomething(); } }
2)过程解析(此处仅分析 Child child = new Child() 这一行代码,对child.num = 20; child.doSomething()不做说明。)
a)对象属性初始化,Child类的num属性初始化值为0。(对应 c)中出现的doSomething方法,0 )
b)加载静态代码块,以 爷 --> 父 --> 子的顺序,故依次输出:
爷类静态代码块 父类静态代码块 子类静态代码块
c)加载构造代码块、构造方法,以 爷 --> 父 --> 子的顺序输出。另,因此处是对Child类进行实例化,故doSomething方法调用的是Child类的。故依次输出:
爷类构造代码块1,25 爷类构造代码块2,21 子类doSomething方法1,0 子类doSomething方法2,13 爷类构造方法1,21 爷类构造方法2,32 子类doSomething方法1,13 子类doSomething方法2,13 父类构造代码块1,5 父类构造代码块2,1 子类doSomething方法1,13 子类doSomething方法2,13 父类构造方法1,1 父类构造方法2,2 子类doSomething方法1,13 子类doSomething方法2,13 子类构造代码块1,10 子类构造代码块2,11 子类doSomething方法1,11 子类doSomething方法2,13 子类构造方法1,13 子类构造方法2,12 子类doSomething方法1,12 子类doSomething方法2,13
3)完整结果输出如下所示:
------------------------测试开始------------------------ 爷类静态代码块 父类静态代码块 子类静态代码块 爷类构造代码块1,25 爷类构造代码块2,21 子类doSomething方法1,0 子类doSomething方法2,13 爷类构造方法1,21 爷类构造方法2,32 子类doSomething方法1,13 子类doSomething方法2,13 父类构造代码块1,5 父类构造代码块2,1 子类doSomething方法1,13 子类doSomething方法2,13 父类构造方法1,1 父类构造方法2,2 子类doSomething方法1,13 子类doSomething方法2,13 子类构造代码块1,10 子类构造代码块2,11 子类doSomething方法1,11 子类doSomething方法2,13 子类构造方法1,13 子类构造方法2,12 子类doSomething方法1,12 子类doSomething方法2,13 子类doSomething方法1,20 子类doSomething方法2,13 ------------------------测试结束------------------------
2、爷 --> 父 --> 子,其他:
1)代码如下,要求列出输出结果:
/** * 父类 */ public class Parent extends GrandParents{ OtherClass other = new OtherClass(); static { System.out.println("static code : parent"); } { System.out.println("Parent 构造代码块"); } Parent() { System.out.println("parent"); } public static void main(String[] args) { new Child(); } } /** * 不相干的其他类 */ class OtherClass { OtherClass() { System.out.println("Other Class"); } } /** * 子类 */ class Child extends Parent { OtherClass other = new OtherClass(); static { System.out.println("static code : child"); } Child() { System.out.println("child"); } } /** * 爷类 */ class GrandParents{ static { System.out.println("static code : grandParents"); } { System.out.println("grandParents 构造代码块"); } GrandParents(){ System.out.println("GrandParents"); } }
2)过程分析:
a.main()方法在Parent类中,故要执行main()方法,需要将Parent类加载到内存中,即会先执行Parent类的“静态属性”和“静态代码块”。根据概念2所提到的,因Parent存在父类GrandParents,故此时实际是先执行GrandParrents静态代码块,再执行Parent静态代码块。所以输出如下所示:
static code : grandParents static code : parent
b.初始化完成,执行main()方法,此时执行new Child()。
c.根据例题1,可知,此时应按照 爷 --> 父 --> 子 的顺序加载静态代码块。但由于GrandParent和Parent的静态代码块已经执行过一次,故此处仅执行Child类的静态代码块(参考概念1中第二点)。故此处输出如下所示:
static code : child
d.初始化属性,加载构造代码块、构造方法(以 爷 --> 父 --> 子的顺序),对于爷类来说,此处输出2行代码;对于父类来说,此处初始化属性调用了new OtherClass(),故会先执行OtherClass中的构造方法,再执行父类的构造代码块、构造方法,此处输出3行代码;子类同理(输出2行代码)。【注:属性、构造代码块、构造方法,哪个写在前面,先执行哪个。概念中已对此点做过说明。】输出结果如下所示:
grandParents 构造代码块 GrandParents Other Class Parent 构造代码块 parent Other Class child
3)完整结果如下所示:
static code : grandParents static code : parent static code : child grandParents 构造代码块 GrandParents Other Class Parent 构造代码块 parent Other Class child
三、结论篇
1、实例化过程(按 爷 --> 父 --> 子的顺序):对象空间的分配,属性默认初始化 ---> 静态代码块初始化 ---> 属性、构造代码块、构造方法初始化(哪个写在前面先初始化哪个)。
2、以上例题中刻意忽略了括号中的内容:哪个写在前面先初始化哪个。有兴趣的可自行测试。