Java中的继承真的发生了什么?

Har*_*pta 22 java inheritance

假设我有两个班Parent,并ChildChild来自继承Parent.我有三种方法Parent,其中两种是公开的,一种是私人的.

通常我们说所有非私有方法都被继承到Child类中,但我对确切发生的事情感到困惑.Java是否在Child类中复制了这些方法,还是使用某种引用来维护关系?

class Parent{
    // Private method
    private void method1(){
        System.out.println("In private method of Parent class");
    }
    void method2(){
    // calling private method
        method1();
    }
    void method3(){
    // calling private method
        method1();
    }
}

class Child extends Parent{

}

class MainClass{
   public static void main(String[] args){
       Child child = new Child();
       // calling non-private method which internally calls the private method
       child.method2();
   }
}
Run Code Online (Sandbox Code Playgroud)

Nav*_*vin 9

Java是否在Child类中创建了方法的副本,还是使用某种引用来维护关系?

后者.看一下invokespecial.Java递归地一次查找一个类的方法.调用第一个匹配方法.这就是调用虚拟(默认)方法比调用final方法慢的原因.

通常,从包含该方法的类继承时,不会复制方法中的实际代码.

  • 您链接的文档描述了决定调用哪个方法的逻辑过程,但这不是它在运行时发生的方式(即使它听起来像文本中那样).通过*类类型*的引用调用方法不涉及父类中的递归搜索.在这种情况下,只需要在类vtable中进行数组查找即可找到正确的方法,因此速度非常快.通过*接口类型*的引用调用方法的执行与您描述的类似,但搜索的结果是高速缓存的,因此只执行一次. (3认同)
  • 即使实现使用递归查找,也没有理由调用“final”方法比其他方法更快。查找开始时,不知道目标方法是否为“final”。当查找找到匹配项时,查找过程结束,发现匹配方法是“final”将不会返回查找该方法所花费的时间。 (2认同)

Hol*_*ger 5

都不是.

继承是一种编程语言概念,而不是一种真正的行为.当您研究编译的Child类时,例如javap,您将找不到与这三种方法相关的任何工件Parent.将Child有超类的信息Parent,但没有提到继承的方法,既不作为引用也不作为副本.

当您实际尝试通过编译时类型的变量调用其中一个Child方法时,概念上继承方法的事实Parent就会发挥作用,就像在您的类中一样.然后由编译器来查找继承的方法,以便正确编译包含调用的代码.编译器是否在每次解析调用的目标方法时搜索类层次结构,或者它是否收集某些数据结构中的现有方法以加速后续查找以及它使用哪些数据结构也取决于编译器.ChildTest

这个过程比你想象的要复杂得多.可能存在多个候选者,其中编译器必须选择一个候选者,并且它甚至可能由于模糊而失败.该过程的结果将是关于调用是否有效以及如果它是有效的,单个目标方法及其实际签名的决定.

编译的调用将包含目标方法的名称和签名,以及对调用它的引用的编译时类型的引用,即Child.它不包含实际方法从中继承的信息Parent.这是故意的.是否Child声明方法本身,从Parent或继承它的方法不Parent应该影响调用者的代码,Test也不应该影响编译的类文件之间的兼容性.


这意味着在运行时类似的类Test可能包含一个由名称和签名给出的引用,而Child不是存储在类文件中的方法Child,换句话说,JVM也负责,使继承的概念工作.同时,它必须确保在执行方法调用时覆盖的概念起作用.

它实现这一点的方式也未明确,为不同的策略留出了空间.在每次调用时搜索类层次结构都是合法的,但会导致性能不佳.

一种常见的技术是vtable.与每个类相关联的是一个表(数组),其中包含对可用方法的引用,无论是声明还是继承.在初始化子类时,它的表将以超类'表的副本开头,在末尾附加新方法的条目,并且更改了重写方法的条目.在某些时候,方法调用将通过查找适合于指定名称和签名的vtable条目的索引来链接.典型的策略是在第一次调用时进行查找.然后修改指令以引用索引以避免后续查找.从那时起,后续执行包括获取对象的运行时类的vtable,并从表的条目中获取方法引用.

考虑到这一点,你可以说在运行时,继承通常被实现为某种引用 - 但等待.

像Oracle的JVM这样的实现能够进行热点优化,其中考虑了经常执行的代码的上下文.例如,可能存在一个继承的方法,它本身调用在某些子类中被重写的方法.当JVM发现在单个具体子类上经常调用此方法时,它可能会为该特定情况创建优化版本.然后,方法代码的副本将成为后续代码转换的起点,内联特定子类的代码等.

由于此类复杂的实现将使用非优化代码的引用,而在其他情况下使用优化副本,因此初始答案答案的替代方法可能是:

都.