今天学习了java中继承期间父子类型的初始化顺序以及重写方法的调用规则,这个知识点比较抽象,理解起来也比较复杂。根据自己的学习、自我理解、总结,用来sharing~
由于这边不是java的运行环境不能使用debug查看具体的执行顺序,所以在以下演示代码中,将使用红色序号标志重要的行,在分析中会更容易理解、明了。
演示代码如下:此代码的输出值为:0
package util;
public class Demo4 {
public static void main(String[] args) {
(1)new Sub();
}
}
class Super{
(2) int a=5;
(3)public Super(){
//执行属性初始化赋值
(4)this.test();//重写方法的调用
}
(5)public void test(){
(6)System.out.println(this.a);
}
}
class Sub extends Super{
(7)int a=6;
(8)public Sub(){
(9)super();
//执行初始化
}
//此处的test与父类中的test方法是重写方法(父子类中,方法名相同,参数列表相同)
(10)public void test(){
(11)System.out.println(this.a);
}
}
分析:
从main开始分析:
当执行(1)new Sub()时,内存会在堆中分配空间,此案例是父子类的继承,则会先分配父类型空间,分配给一个变量a,然后分配给子类型空间,分配给一个a,在java中所有的对象的空间分配都有一个默认值0,因此初始时的两个a的值均为0,如上图中底下初始值所示。
为什么new的是子类型对象还会有父类型对象a的出现?
answer:在java中,创建子类型对象时,将父类型与子类型的对象一并会创建出来,创建的是一个对象链。在父子类型继承时,创建的是一个对象,但这一个对象是一个整体,如洋葱,互相嵌套,若是8个继承关系的情况下,也就是需要套8层,但终究包含在同一个对象里。
执行完(1)后才开始调构造器,在java中,先分配空间再调构造器。此案例中的new Sub()表明首先调用的是Sub子类的构造器。Sub子类中有一个默认的构造器(默认构造器就是没有参数的构造方法),就是第(8)句,它里面有一个super();原因是:子类型一定调用父类型的构造器。
其实在java执行中,(8)和(9)可以不用写,默认都有,只是在这边为了更清楚的说明执行顺序而加入。此时执行顺序是:(1)-->(8)。
在第(8)居中执行super()时(1)-->(8)--(9)又跳到父类型Super方法中执行第(3)句,此时也就是在Sub方法中执行Super.(1)-->(8)-->(9)-->(3),在执行(3)时将执行属性的初始赋值,也就是此时将a的值赋为5,(1)-->(8)-->(9)-->(3)-->(2)-->(4),然后执行this.test();(1)-->(8)-->(9)-->(3)-->(2)-->(4)。
this是一个引用类型变量,属于局部变量,存放在栈中,然后它又是引用,引用了当前对象,此时的对象为一个对象,就是Sub对象,如图中的红线所示。其中的test()为重写的方法调用,调用的是当前类型所对应的方法,当前this所对应的对象是Sub,所以这边调用跨的幅度比较大,比较有趣,调用Sub中的test()方法(1)-->(8)-->(9)-->(3)-->(2)-->(4)-->(10),此时的test()方法中有一个输出语句(11),此时的顺序为(1)-->(8)-->(9)-->(3)-->(2)-->(4)-->(10)-->(11),这个输出语句中的this.a是当前对象中的a,当前对象为Sub,Sub中的a值为0,因此此时输出的结果为0,此时的a输出是在赋值6之前输出的,所以在控制台看见的输出结果为0.
Sub中的test方法执行完后,应该返回到它的调用者,到Super中的this.test()即为:(1)-->(8)-->(9)-->(3)-->(2)-->(4)-->(10)-->(11)-->(10)-->(4),此时构造器的执行已全部结束。
构造器执行结束后,程序应该返回到的是调用构造器的地方第(9)句的super();即为:(1)-->(8)-->(9)-->(3)-->(2)-->(4)-->(10)-->(11)-->(10)-->(4)-->(9)在super结束时,也就是第(9)句的下面执行初始化,此时的Sub中的a被赋值为6,(1)-->(8)-->(9)-->(3)-->(2)-->(4)-->(10)-->(11)-->(10)-->(4)-->(9)-->(7),但是此时的输出早已结束,因此控制台最终的输出结果不是6.
Conclusion:
1.java中凡是new出来的都会分配空间,父类和子类的属性都会被分配出来,在堆中存放。
2.局部变量在栈中存放,引用了堆中的对象。
3.创建子类型对象时,将父类型与子类型的对象一并会创建出来,创建的是一个对象链。在父子类型继承时,创建的是一个对象,但这一个对象是一个整体,如洋葱,遵循互相嵌套。
4.继承期间父子类型的初始化顺序:(用以上案例的分析结果做总结)
分配空间,new 出一个子类型的对象后,调用new出对象的构造器即子类型的构造器,然而子类型的构造器中必须调用父类型的构造器,父类型的构造器在执行初始需要给属性赋初值,然后根据父类型构造器中的方法依次执行。同理,子类型的构造器中调用执行完父类型的构造器后同样给属性赋初值。
注意:子类型的构造器在调用执行完父类型构造器之后执行属性初始赋值,父类型的构造器被子类型调用时的最初(执行构造器中的方法之前)执行属性初始赋值。
5.重写方法的调用规则:
在程序的执行中遵循:先分配空间,在调构造器,子类型一定调用父类型的构造器,父类型的构造器被调用执行结束后应该返回到的是子类型调用的地方。
————能力有限,请海涵,希望与更多了解的friends讨论。