Welcome 微信登录

首页 / 操作系统 / Linux / Java初始化过程

这篇文章主要讲解Java在创建对象的时候,初始化的顺序。主要从以下几个例子中讲解:
  • 继承关系中初始化顺序
  • 初始化块与构造器的顺序
  • 已经加载过的类的初始化顺序
  • 加载父类,会不会加载子类
  • 创建子类对象会不会创建父类对象

例子1——继承关系中初始化顺序

先看简单的情况,看下面的例子:public class Father {public String fatherVar = "父类构造块初始化";public static int fatherStaticVar;public int i;static {int i = 100;System.out.println("父类静态块初始化,i的值为" + i);System.out.println("父类静态变量初始化,fatherStaticVar的值为" + fatherStaticVar);}{System.out.println(fatherVar);}public Father(){System.out.println("父类构造函数的初始化,i的值" + i);}}public class Son extends Father {public String sonVar = "子类构造块初始化";public static int sonStaticVar;public int i;static {int i = 101;System.out.println("子类静态块初始化,i的值为" + i);System.out.println("子类静态变量初始化,sonStaticVar的值为" + sonStaticVar);}{System.out.println(sonVar);}public Son(){super();System.out.println("子类构造函数的初始化,i的值" + i);}public static void main(String[] args) {new Son();}}其执行的结果如下:父类静态块初始化,i的值为100父类静态变量初始化,fatherStaticVar的值为0子类静态块初始化,i的值为101子类静态变量初始化,sonStaticVar的值为0父类构造块初始化父类构造函数的初始化,i的值0子类构造块初始化子类构造函数的初始化,i的值0按照结果,我们可以知道在有继承的时候,虽然是创建一个Son对象,但是JVM发现Son对象的类还没有装载,而Son类又继承自Father类,只有加载了Father类,才能加载Son类。于是加载Father类的时候,就会初始化一切静态变量和静态块。所以上文结果中第一行和第二行是父类静态变量和静态块初始化的结果,然后加载完Father类之后,又会加载Son类,同样是初始化Son类的静态块和静态变量,出现上文中第三行和第四行的结果。等这个2个类都加载完了,才开始创建Son对象,因为Son对象,显示调用了Father类的构造器,所以先执行Father类的构造器,出现第五行和第六行的结果,等Father类构造器执行完了,才执行后续Son构造器的内容,所以最后出现了第七行和第八行的结果。

例子2——初始化块与构造器的顺序

在上面的例子中,有2个语句块叫初始化块。在上文的结果中是初始化块的执行是先于构造器的,现在看一下把初始化块的内容放到构造器下面,会是什么的结果public class InitBlock {public InitBlock(){System.out.println("构造器在执行......");}{System.out.println("初始化块1在执行......");}{System.out.println("初始化块2在执行......");}public static void main(String[] args) {new InitBlock();}}结果如下:初始化块1在执行......初始化块2在执行......构造器在执行......很显然,无论初始化块写在哪个地方,都是先于构造器执行的,但是初始化块之间的顺序是前面的先初始化,后面在初始化。

例子3——已经加载过的类的初始化顺序

更改一下例子1中的main方法,改成如下:public static void main(String[] args) {new Father();System.out.println("=============");new Son();}结果如下:父类静态块初始化,i的值为100父类静态变量初始化,fatherStaticVar的值为0子类静态块初始化,i的值为101子类静态变量初始化,sonStaticVar的值为0父类构造块初始化父类构造函数的初始化,i的值0=============父类构造块初始化父类构造函数的初始化,i的值0子类构造块初始化子类构造函数的初始化,i的值0结果很有意思,创建父类对象的时候,加载Father类,出现第一行和第二行的结果,但是这个竟然会还把子类的静态变量和静态块初始化?这个原因,例子4在说。 最后执行父类的构造器创建父类对象。当再创建子类的时候,发现父类和子类已经加载过了,所以不会再加载Father和Son类,只会调用父类的构造器,再执行后续子类构造器的内容,创建子类。

例子4——加载父类,会不会加载子类

用一个崭新的例子来看看上面,创建父类的时候,为什么会打印出子类静态初始化执行的结果。public class StaticFather {static{System.out.println("父类静态初始化块");}}public class StaticSon extends StaticFather{static {System.out.println("子类静态初始化块");}}public class Test {public static void main(String[] args) {new StaticFather();}}结果如下:父类静态初始化块这次就不会创建父类的时候,加载子类。例子3之所以出现这个原因 是因为main函数在子类中写的,要执行main函数必须要加载子类。只会加载子类之前要先加载父类,因为不加载父类,只加载子类,怎么让子类调用父类的方法和变量。但是加载父类不会加载子类,反正父类也调用不了子类的方法。

例子5——创建子类对象会不会创建父类对象

做个实验,看一下创建子类对象的???候,到底会不会创建一个父类对象,先说结论:不会。从道理上讲,如果创建任何一个对象都要创建出一个他的父类对象的话,那么整个JVM虚拟机都是Object对象。看下面的实验:public class ObjectFather {public void getInfo(){System.out.println(getClass().toString());}}public class ObjectSon extends ObjectFather{public ObjectSon(){super();super.getInfo();}public static void main(String[] args) {new ObjectSon();}}结果如下:class com.byhieg.init.ObjectSon可以看出来,创建子类对象时那个父类的Class还是子类的,也就是说创建子类对象并没有创建一个父类的对象,只是说调用了父类的构造器,对父类的属性进行初始化,并且给子类提供了一个super指示器去调用父类中那些变量和方法。
更详细的说,new一个对象实际上是通过一个new指令开辟一个空间,来存放对象。在new ObjectSon()的时候,就只有一个new指令,只会开辟一个空间,所谓初始化父类等等,都是在这个空间中有一个特殊的区域来存放这些数据,而super关键字就是提供了访问这个特殊区域的方法,通过super去访问这个特殊区域。
还可以比较super和this的hashcode来判断,结果必然是两者的hashcode是一致的。

总结

至此,Java初始化的讲解到结束了,基本了覆盖了绝大多数情况中的初始化。本文永久更新链接地址:http://www.linuxidc.com/Linux/2017-01/139352.htm