向上转型底层原理分析

向上转型

向上转型在写代码的过程中经常会用到,它不仅是实现多态的重要机制,也是Java继承体系的精华体现。
向上转型常见的形式是:Person person = new Student();这里的Student类是Person类的子类,我们通过Student类的实例构建了一个父类的对象。在方法的调用层面上,person对象只能调用在Person类中存在的方法,且其中的方法一旦被子类重写,那么就会优先调用子类中被重写的方法。或许在运用层面明白这些就足够了,但是在算法的设计以及JVM调优中,只是会运用是远远不够,还需要理解其底层的原理。接下来让我们在JVM层面去理解向上转型的方法调用过程。

方法表的概念

继承过程图示
在这里插入图片描述
简述:假设存在这样一个继承关系,Object类被子类1继承,子类1又被子类2继承。图示展示了在继承过程中方法和方法表的变化。每当用到一个类,如果它还没有被加载,那么就会把类的信息翻译为字节码记载到JVM相应的区域(每个类只加载一次)。
1、Object类加载:如图,我们先new一个Object对象,JVM会先在堆中申请一块存储空间存放Object类的方法,然后在方法区生成一张方法表,方法表中有指针,指向这些方法,在需要调用这些方法的时候,就可以通过方法表来索引方法。
2、然后让子类1继承Object类,同样,会为子类1在堆中开辟一块存储空间来存放子类1的方法(是指代码中写在子类1中的那些方法),然后生成子类1的方法表,方法表首先会继承一份自父类(类似于复制),父类有的方法子类都会获得,且子类和父类的同名方法在方法表上的位置偏移量完全相同,这时,我们再根据子类下写的那些方法来调整方法表,如果子类重写了一个父类的同名方法,那么指针会修改为指向子类的方法存储区域的那条被重写后的方法,如果子类新增了一个方法,那么就会在方法表的某处添加一个指针,指向子类新开辟的方法存储区的那条新增加的方法。如图子类1继承了Object类的两个方法,新增了两个方法。
3、子类2继承子类1,同理,它会开辟空间存储自己的方法,继承子类1的方法表,然后做出调整,两条引用依然执行Object类的方法;图中黑色标识处表示它重写了子类1的同名方法(相同偏移位置),指针指向自己重写后的方法;继承了一个子类1的方法;新增了一个自己的方法。
小结:每个类都有一张自己的方法表,方法表不仅与类下关于方法的代码有关,还与继承的父类有关。获得子类方法表之前,必定经历父类方法表的创建。方法表是类的对象索引方法的途径,对之后理解向上转型机制帮助很大。

向上转型的方法调用机制

在理解了上面的原理后,我们来分析向上转型过程,依然以:Person person = new Student();为例。
1、类加载:执行这段代码,若类没有被加载过,就会执行类的加载过程,根据前面的分析我们可以知道,类的方法表的形成是基于父类的,所以,如果存在继承关系,是要从继承关系的顶端的类,自上而下逐个类来加载的,在不断地修正中形成每个类的方法表。
2、得到person对象:通过new了Student()获得Student的实例,在“=”左侧自动转型得到的是Person类的对象person。
3、可调用的方法:假设Person类study()方法和work()方法,Student类有study()方法和play()方法。其中study()方法是相同的,Student()类重写了父类的study()方法。person对象可以调用的方法是study()方法和work()方法,为什么呢?因为它是被申明为Person类的对象,于是我们得到的是Person类的方法表。
4、执行调用:让person对象执行study()方法,JVM会先查找Person类的方法表,判断方法表中是否有这个方法(这解释了为什么自动转型只能调用父类中存在的方法名),如果有则判断当前对象的实例是哪个子类的,如果是自己,就直接查自己的方法表调用自己方法表中该引用指向的方法,如果当前实例是自己的子类,就查找子类的方法表,然后找到相同的偏移量位置的指针并调用方法,这个方法可能是被重写的,有可能是被继承下来没有被重写的。在例子中,调用的是被Student类重写的Study()方法。

猜你喜欢

转载自blog.csdn.net/mayifan_blog/article/details/85935641