在我的下面这篇博客(链接如下)中有几个例题,其中关于父类构造函数中调用了子类中重写方法的过程中,到底发生了什么,我原文博客里也有解释,由于篇幅挺大,为了方便阅读和体现专题性,所以将其从原文中拿出来,单独写成博客 。
本人博客原文:https://blog.csdn.net/weixin_37766087/article/details/94489132
由一个示例引发的血案,查了很多博客,都没有给出很清晰的解释,下面记录自己的分析过程。
下面是示例的源代码:
public class App {
public static void main(String[] args)throws Exception{
Sub b=new Sub();
b.callName(); //这一步是多态的 使用,很好理解
}
}
class Base{
private String baseName="base";
public Base(){
System.out.println("hellobase");
callName();
}
public void callName(){
System.out.println(baseName);
}
}
class Sub extends Base{
private String baseName="sub";
public void callName(){
System.out.println("hello");
System.out.println(baseName);
}
}
}
结果输出是null 为什么???
Base b = new Sub();先初始化父类,然后调用子类的callName方法时子类的属性还没有初始化执行代码,所以打印的是null.
为了解决上面的疑问,可算是费劲苦心,好在结果不错,有过程,有结论,最起码说服了自己。
(经过个人的调试,终于发现了构造类时 变量的初始化顺序的秘密)
为了方便,本人重新写了一份代码,下面记录我的代码执行顺序:
1 public class ForTest {
public static void main(String[] args){
2 A b=new B(); //加上断点
}
}
class A{
3 int a=3; //加上断点
public A(){
//默认super() //这个也可以显示写出来,并加上断点,会发现,第一步走super()
4 System.out.println(a); //加上断点
5 method();
}
7 public void method(){
6 System.out.println(a);
}
}
class B extends A{
8 int a=2; //加上断点
public B(){
9 System.out.println("hello");
10 System.out.println(a);
}
public void method(){
11 System.out.println(a); //加上断点
}
}
从上一步到下一步都是点击step over:
注:下面每一步的行对应的是上方代码区里面的标注行,有标注,共11个标注行。
step 1:进入调试 代码在 行2 处
step 2: 代码在行3 处。
想一下为什么一下就到了父类A中的变量赋值处?实际上在new子类的时候,调用了父类A的构造函数,你所看到的是一下子就到了父类A中的变量初始化这儿,也就是说变量在new一个类对象的时候,先执行的变量初始化的,再执行构造函数,如果仅仅是这样记忆,又会产生疑惑,你肯定会想,那这里在从父类构造函数中调用子类的callName的时候,肯定能打印出a=2 这个值啊,但是实际上打印的是0,也就是说此时的a还是默认初始值0,那难道上面说的new对象的时候 先执行变量初始化是错的???实际上,一般情况下,不用考虑父类构造函数调用子类重写的方法的时候,这样记忆确实是对的,但是原理是什么,原理这样是错的,原理上,并不是初始化先执行,下面给出我的分析:
根本原因在于,这里忘了分析最最关键的一步,实际上,new类对象的时候的时候,最先执行的还是构造函数,这里执行的一定是构造函数的第一步super()函数,它去首先构造了父类的对象,所有类都是Object的子类,所以会一直向上构造父类,直到Object,当父类构造完毕,super执行完毕,再返回本类中,从代码块,变量的初始化开始执行,这里要注意,此时,本类构造函数super()函数执行完毕,并没有继续执行构造函数中super()函数下面的语句,而是去执行了变量的初始化,然后当构造代码块和变量初始化完毕,再继续执行构造函数super()以下的语句,所以导致一种假象,我们看起来就是先执行了构造代码块和变量的初始化,最后再执行构造函数,实际上是第一步执行了构造函数,但是只执行了super(),它去构造父类对象去了。
为了验证我上面的分析,在new一个对象的时候,我们可以将super()函数显示写在构造函数中,再打上断点,你会发现,一定是先执行super()函数(如果该类父类不是Object,那么就会走到父类的构造函数,如果该类的父类是Object,也会走到父类Object,只不过Object是一种特殊的类,与jvm底层相关),然后等父类构造完毕之后,才会执行变量的初始化。
step3: 到达4 ,此时打印a,值为3
step 4 :到达5,
step 5: 到达11 ,这里父类构造函数中调用的重写方法,是子类的,为什么?看原文博客(博客链接在第一行)示例3后面的解析。注意,此时a的输出是0,为什么??经过step2的分析可知:此时父类对象还没有构造完成,也就是说B中构造函数中的super()还没有执行完,此时,并不会进行B中变量的初始化,所以a是默认的初始值0.
step 6: 到达8 ,此时,才会对变量a进行赋值,B的父类已经构造完毕。
step7: 到达9 ,step7和step8是执行构造函数中super剩下的步骤。
step8: 到达10
step 9:到达2 ,至此,新对象B的构造过程才算是结束!
图解:为了将上述执行顺序看得更加清楚,将每一步标注成红色的序号,如下:
红色的标号是断点执行顺序,凡事标注红色序号的代码处都要打上断点才能显示顺序,这里有些地方没有打断点。
总结:
new构造函数的时候
1 先执行构造函数中的super()函数,它去构造父类对象;
2 构造完毕之后,返回本类执行变量和构造代码块的内容;
3 继续执行构造函数中super()以下的内容。