Java - 继承实现

时间:2014-03-11 17:11:51

标签: java inheritance jvm polymorphism

让我们考虑简单的界面:

interface Simple{
     void doSth();
}

实现它的两个类:

class A implements Simple{

     void someOtherMethod(){ .... }

     void doSth(){ ... }

     private void doSth(int x){ ... }
}

class B implements Simple{

     void methodA(){ ..}
     // many other methods

     void doSth(){ ... }

     private void doSth(Object o, long y){ ... }
}

现在,我可以轻松地写道:

Simple s = new A();
s.doSth();

Java的多态性将完成其余的工作。有谁知道如何Hotspot,确保链接器将链接到正确的方法,考虑到在实现类中可以有更多定义,甚至它们的返回类型可以是原始的子类? Java是否确保接口方法始终以vtable中的某个偏移量开始,例如在0?

2 个答案:

答案 0 :(得分:2)

在我们投资之前,让我们简化一下这个例子:

interface Foo {
    void bar();
}

class AFoo implements Foo {
    int i;

    @Override
    public void bar() {
        i++;
    }
}

class AnotherFoo implements Foo {
    int i;

    @Override
    public void bar() {
        i--;
    }
}

public class Test {
    public static void main(String[] args) {
        Foo foo = new AFoo();
        foo.bar();
    }
}

编译完成后,我们使用

  

javap.exe -verbose Test.class

检查生成的字节码:

  public static void main(java.lang.String[]);
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=2, args_size=1
         0: new           #16                 // class tools/AFoo
         3: dup
         4: invokespecial #18                 // Method tools/AFoo."<init>":()V
         7: astore_1
         8: aload_1
         9: invokeinterface #19,  1           // InterfaceMethod tools/Foo.bar:()V
        14: return

在类加载时,代码将链接,Java语言规范为specified,如下所示:

  

类或接口的二进制表示使用其他类和接口的二进制名称(第13.1节)(第13.1节)以符号方式引用其他类和接口及其字段,方法和构造函数。对于字段和方法,这些符号引用包括字段或方法所属的类或接口类型的名称,以及字段或方法本身的名称以及相应的类型信息。

     

在可以使用符号引用之前,它必须经过解析,其中检查符号引用是正确的,并且通常用直接引用替换,如果重复使用引用,则可以更有效地处理该引用。

请注意,此“直接引用”是指方法的声明。如果有多个实现,则运行时此时不能知道将使用哪种方法。也就是说,在Java语言规范调用链接期间,但在执行实际方法调用表达式时,不会解析多态性。这是Java虚拟机规范的specified

  

设C为objectref的类。要调用的实际方法由以下查找过程选择:

     
      
  • 如果C包含与已解析方法具有相同名称和描述符的实例方法的声明,则这是要调用的方法,并且查找过程终止。

  •   
  • 否则,如果C具有超类,则使用C的直接超类递归地执行相同的查找过程;要调用的方法是递归调用此查找过程的结果。

  •   
  • 否则,会引发AbstractMethodError。

  •   

由JVM实现如何实际实现它。对于Oracle Hotspot JVM,文档包含rather detailed explanation

  

当链接invokeinterface调用时,链接器会在接口中解析对抽象目标方法的调用。这归结为目标接口和该接口中的所谓可用索引。

     

JVM验证程序永远不会保证目标接口的静态;每个invokeinterface接收器都被键入一个简单的对象引用。因此(与invokevirtual调用不同),不能对接收者的vtable布局做出任何假设。相反,必须更仔细地检查接收者的类(由其_klass字段表示)。如果虚拟调用可以盲目地执行两个或三个间接以到达目标方法,则接口调用必须首先检查接收者的类以确定(a)该类是否实际实现了接口,以及(b)如果是,那么接口的方法记录在该特定类别中。

     

没有简单的前缀方案,其中接口的方法在实现该接口的每个类中的固定偏移处显示。相反,在通用(非单态)情况下,汇编编码的存根例程必须从接收器的InstanceKlass获取已实现接口的列表,并遍历该列表以寻找当前目标接口。

     

一旦找到该接口(在接收者的InstanceKlass中),事情变得容易一些,因为接口的方法被安排在一个itable或“接口方法表”中,一个方法的显示,其槽结构对于每个实现有问题的接口的类。因此,一旦在接收者的InstanceKlass中找到接口,关联的偏移量就会将程序集存根指向嵌入InstanceKlass中的itable(就像人们所期望的那样在vtable之后)。此时,调用与虚拟方法调用一样进行。

     

几乎相同的优化适用于接口调用以及虚拟调用。与虚拟调用一样,大多数接口调用都是单态的,因此可以通过廉价检查呈现为直接调用。

     

以下是多态接口调用的通用指令跟踪:

callSite:
    set #calledInterface, CHECK
    call #itableStub[itableSlot]
---
itableStub[itableSlot]:
    load (RCVR + #klass), KLASS_TEM
    load (KLASS_TEM + #vtableSize), TEM
    add  (KLASS_TEM + TEM), SCAN_TEM
tryAgain:
        # this part is repeated zero or more times, usually zero
    load (SCAN_TEM + #itableEntry.interface), TEM
    cmp TEM, CHECK
    jump,eq foundInterface
    test TEM
    jump,z noSuchInterface
    inc #sizeof(itableEntry), SCAN_TEM
    jump tryAgain
tryAgain:
    load (SCAN_TEM + #itableEntry.interface), TEM
    cmp TEM, CHECK
    jump,eq foundInterface
foundInterface:
    load (SCAN_TEM + #itableEntry.offset), TEM
    load (KLASS_TEM + TEM + #itableSlot), METHOD
    load (METHOD + #compiledEntry), TEM
    jump TEM
---
compiledEntry:
    ...
  

总之,这是六个内存引用和两个非本地跳转。

迂腐:以上所有内容都适用于调用接口方法。调用在类中声明的抽象方法使用different bytecode instruction和稍微简单implementation in the Oracle Hotspot JVM

答案 1 :(得分:-1)

interface InterfaceA{void method();}
class ClassA implements InterfaceA{void method(){}}
void methodA(InterfaceA[]o){
    for(int i=0;i<o.length;++i)o[i].method();
}
void methodB(ClassA[]o){
    for(int i=0;i<o.length;++i)o[i].method();
}

所以调用方法比调用方法

慢得多