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

时间:2015-09-08 10:30:32

标签: java inheritance

假设我有两个类ParentChildChild继承自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();
   }
}

3 个答案:

答案 0 :(得分:9)

  

Java是否复制了Child类中的方法,还是使用某种引用来维护关系?

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

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

答案 1 :(得分:5)

都不是。

继承是一种编程语言概念,而不是真正的行为。在调查已编译的Child类时,例如使用javap,您将找不到与Parent的三种方法相关的任何工件。将会有Child具有超类Parent的信息,但没有提及继承的方法,既不作为引用也不作为副本。

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

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

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

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

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

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

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

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

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

两个

答案 2 :(得分:3)

方法体不会复制到子类的未定义方法体中。相反,当你打电话

Child child1 = new Child();
child1.method1();

它将查看其层次结构,每次无法找到实现时上升到某个级别。首先,它将检查没有实现的Child。高于那个父级的一级,所以它将使用那个。正如上面@Dante正确提到的那样,这是通过子类的构造函数中的super()调用实现的。此图片可能会帮助您获得更好的图片: enter image description here

我认为你的困惑在某种程度上与这一点有关 非私有方法的继承。所以,我也想解决这个问题

为什么不会继承私人会员?

你可以说私有成员不是继承的,因为Child无处可以明确地引用值。即this.value之类的任何代码都不能在Child中使用,也不能在某些调用代码中使用obj.value(显然)。 但是,从另一个意义上说,你可以说价值是继承的。如果您认为Child的每个实例也是Parent的实例,则该对象必须包含'value'中定义的Parent。即使Child类对它一无所知,私有成员名称值仍然存在于Child的每个实例中。所以在这个意义上,你可以说价值是继承的#34;在Child中。所以,如果不使用"继承"这个词,请记住,子类不了解父类中定义的私有成员。但是请记住,那些私有成员仍然存在于子类的实例中。

注意:JLS声明(http://docs.oracle.com/javase/specs/jls/se5.0/html/classes.html#8.2):

  

声明为private的类的成员不会继承   该类的子类。仅声明的类的成员   protected或public由包中声明的子类继承   除了宣布上课的那个。