Java 的继承是比较重要的特性, 也是比较容易出错的地方, 下面这个例子将展示如果父类构造器中调用被子类重写的方法时会出现的情况:
首先是父类:
- public class test {
- void fun(){
- System.out.println("test fun()");
- }
- void fun1(){
- System.out.println("test fun1()");
- }
- test(){
- fun();
- fun1();
- }
- public static void main(String[] args) {
- test t =new test();
- }
- }
这里父类的构造器将调用一个 fun 方法, main 函数的运行结果是:
- test fun()
- test fun1()
然后是子类:
- public class test2 extends test {
- int i = 2;
- test2(){
- fun();
- }
- void fun(){
- System.out.println("test2 fun()");
- System.out.println(i);
- }
- public static void main(String[] args) {
- test2 t = new test2();
- }
- }
子类增加了一个字段 i 并初始化为 2, 并重写了 fun 方法, 不仅打印的字符串不一样, 还加了打印 i 的功能, 构造器和父类一样调用了 fun 方法. main 函数的运行结果是:
- test2 fun()
- 0
- test fun1()
- test2 fun()
- 2
通常 java 的类进行初始化的时候, 会先进行父类的初始化, 所以会先调用父类的构造器, 再进行子类的初始化, 调用子类的构造器.
一开始写完代码我以为的结果是:
- test fun()
- test fun1()
- test2 fun()
- 2
我以为就算父类的方法被重写了, 也会调用自己的方法, 但事实告诉我们, 父类初始化过程中构造器如果调用了被子类重写的方法, 会调用被子类重写的方法.
还有一点, 如果子类重写的方法中使用了子类才定义的字段, 那这个字段的值将是该字段类型的默认值.
所以类的初始化流程总结 (继承相关) 就是:
1. 为对象分配的存储空间初始化为二进制零.
2. 调用父类的构造器, 如果调用被覆盖的方法, 被覆盖的方法将被调用, 如果使用了子类中才定义的字段, 该字段的值为该字段类型的默认值.
3. 调用子类的构造器.
(这里总结的初始化流程只总结了继承相关的, 正常的 static 部分, 初始化代码还是正常的样子)
为什么会出现这种情况呢, 为什么需要先为对象的存储空间初始化为二进制零呢?
1. 在继承中构造器的调用是分级的, 先调用父类的, 父类如果有父类就父类的... 这一步是通过动态绑定实现的.
2. 从概念上来说, 构造器是用来初始化对象的, 但是像上面那种情况, 子类重写了父类的方法, 使得父类将使用子类的成员, 但是此时正在初始化父类, 子类还没有进行初始化.
3. 基于以上两点就将为对象分配的存储空间初始化为二进制零.
所以重写父类的方法的时候需要考虑到这个特性, 这种特性可能会导致父类的初始化出现问题.
来源: https://www.cnblogs.com/xxbbtt/p/12484111.html