复杂父子继承相互调用的深入理解

前言

看过像Spring这样的开源框架源码,第一个感觉:逻辑很复杂,一层一层的跳来跳去,用一个实际例子感受一下(这是Springmvc的handleMapping在找对应的requestMapping的过程一部分,这里的展示的复杂的过程中居然只是“无意义”的中转代码,还没走到真正起作用的方法)
image

在跟踪源码的时候,一会儿跳到父类,一会儿跳到子类。有时候会想:这怎么可能。
为什么会产生这种疑惑。因为调用父类我们比较好理解:子类继承父类,就是为了调父类资源的。那父类调用子类呢?
下面深入分析一下这种“子类和父类的相互调用”。 (本文不讨论两个明显孤立的对象之间的调用,即便这两个对象有上下级关系

一般理解

  • 子类调用父类
    为了继承并沿用父类的资源,这个动作合情合理

  • 父类调用子类
    应用:钩子函数/模版方法

猜想

不存在子类调用父类,更不存在父类调用子类
结论:都是自己一个对象之间内部的调用动作
之所以出现调用父类的感觉,是因为没看到子类的本质,实际子类体内只是包含了父类的“基因”
子类调父类方法,调的还是自己体内的方法。
另一方面,父类调用子类也只不过是错觉:一开始的那个做动作的人一定是子类。因为祖先不可能像先知一样在五千年前给你写一封信

Demo

下面看一个刚学java时可能会见到过的一个题目:
以下代码结果是_____。

class Base {
    int i;
    
    Base() {
        add(1);
    System.out.println(i);
    }
    void print() {
        System.out.println(i);
    }
}

class MyBase extends Base {
    MyBase() {
        add(2);
    }
    void add(int v) {
        i+=v*2;
        System.out.println(i);
    }
}

public class TestClu {
    public static void main(String[] args) {
        go(new MyBase());
    }
    static void go(Base b) {
        b.add(8);
    }
}

结果:22

这里的情况,父类构造函数里调用的就是子类的add方法,而不是父类自己的。
根据我们上面猜想,这里并不存在“父类Base”,只有子类对象MyBase。所有的调用全部在子类对象MyBase内部完成,当子类重写里父类的方法,那么就用子类自己的方法。

字节码层面

先写两个简单的对比demo

package com.yc.blog.java.inherit.demo1;

/**
 * 父类
 */
public class Fa {

	public void m() {
		System.out.println("-----Fa---m----");
	}
}
package com.yc.blog.java.inherit.demo1;

/**
 * 继承父类
 */
public class Ch extends Fa {

	public void m1() {
		System.out.println("------Ch---m1-------");
	}
}

package com.yc.blog.java.inherit.demo1;

/**
 * 不继承父类
 */
public class Ch2 {

	public void m1() {
		System.out.println("------Ch2---m1-------");
	}
}

查看并对比Ch和Ch2的字节码
javap -v Ch
javap -v Ch2
image

可以看到两者并没有什么本质的区别,只是一个父类是Fa,另一个是Object。内部并没有增加父类的方法。
其实也比较好理解,jvm字节码安排的这么紧密,就是为了省字节空间,如果子类把“祖宗十八代”的父类全部写到自己的字节码里clas文件里,那也太占空间,这个class文件字节码也太不优美了。

java内存层面

虽然字节码层面还是 子类和父类隔离开,那么运行时是不是new子类的时候就同时new一个对应的父类呢?
不是的,内存层面的设计和上面猜想的设计思想是一致的:只有一个子类,父类的资源只是被加到子类的内存区域里(当然,会加上标签,哪些属于父类的,哪些属于子类的)
这里就不再继续深究,推荐两篇文章大家可以参考一下:
JAVA new子类的时候是否会创建父类
java中,创建子类对象时,父类对象会也被一起创建么?

回头再看Spring源码

和我们的猜想一样,上面那个复杂的Spring调用逻辑,其实就是从左下角那个“重孙”RequestMappingHandleMapping开始调用的,更准确的说,就是“重孙”自己内心的复杂OS,和它的父类们没有关系。

结论

  1. 不存在子类调用父类,父类调用子类的说法,所有上下级的调用都是一个类内部方法的调用,只不过是它内部继承了父类的一些方法。
  2. 当一个方法被几代人重写,那么就用子类自己的,子类没有就往上找,这也就是java的“多态”规则。
  3. 字节码层面子类和父类还是独立分开。
  4. 创建子类的时候并不会同时创建父类,而是在内存层面子类把父类的资源包含到自己的内存空间里。
  5. 框架里常用预留方法给子类重写,作为钩子函数/模板方法。如何理解钩子函数的运作机制,就是上面所解释的。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值