Java如何确定多态性中的方法在运行时调用?

Java如何确定多态性中的方法在运行时调用?,java,jvm,polymorphism,bytecode,Java,Jvm,Polymorphism,Bytecode,虽然多态性的主要原则是从类型方面解耦“what-from-who”,但让我困惑的是,方法调用机制如何在多态性中找到并调用正确的方法体 public class TestRide { public static void ride(Cycle c){ c.ride(); } public static void main(String[] args){ Cycle Cycling = new Cycle(); ride(

虽然多态性的主要原则是从
类型方面解耦“what-from-who”,但让我困惑的是,方法调用机制如何在多态性中找到并调用正确的方法体

public class TestRide {

    public static void ride(Cycle c){
        c.ride();
    }

    public static void main(String[] args){

        Cycle Cycling = new Cycle();
        ride(Cycling);

        Bicycle bi = new Bicycle();
        ride(bi);

        Tricycle tri = new Tricycle();
        ride(tri);

        Unicycle uni = new Unicycle();
        ride(uni);
    }

}
因为在java中,所有的方法绑定都是
后期绑定
,除非方法是
静态的
最终的
私有的
,而后期绑定是由JVM完成的,JVM为每个类预计算
方法表
,然后在运行时在正常的方法调用中查找表

但在多态性期间也会发生同样的事情。比如说

假设我有一个带有
ride()
方法的泛型类
Cycle

class Cycle {

    public void ride(){
        System.out.println("I'm Riding generic Cycle()");
    }

}
我有三个专门的类
Bicycle
Unicycle
,它们扩展了通用类Cycle,并覆盖了它的
ride()
方法

class Bicycle extends Cycle {

    public void ride() {
        System.out.println("I'm riding Bicycle");

    }

}

class Tricycle extends Cycle{

    public void ride() {
        System.out.println("I'm riding Tricycle ");

    }

}

class Unicycle extends Cycle {

    public void ride() {
        System.out.println("I'm Riding Unicycle ");

    }

}
这是测试上述多态性的
TestRide

public class TestRide {

    public static void ride(Cycle c){
        c.ride();
    }

    public static void main(String[] args){

        Cycle Cycling = new Cycle();
        ride(Cycling);

        Bicycle bi = new Bicycle();
        ride(bi);

        Tricycle tri = new Tricycle();
        ride(tri);

        Unicycle uni = new Unicycle();
        ride(uni);
    }

}
输出是

I'm Riding generic Cycle()
I'm riding Bicycle
I'm riding Tricycle 
I'm Riding Unicycle 
字节码:

public static void main(java.lang.String[]);
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=5, args_size=1
         0: new           #17                 // class com/polymorphism/Cycle
         3: dup
         4: invokespecial #24                 // Method com/polymorphism/Cycle."
<init>":()V
         7: astore_1
         8: aload_1
         9: invokestatic  #25                 // Method ride:(Lcom/polymorphism/
Cycle;)V
        12: new           #27                 // class com/polymorphism/Bicycle
        15: dup
        16: invokespecial #29                 // Method com/polymorphism/Bicycle
."<init>":()V
        19: astore_2
        20: aload_2
        21: invokestatic  #25                 // Method ride:(Lcom/polymorphism/
Cycle;)V
        24: new           #30                 // class com/polymorphism/Tricycle

        27: dup
        28: invokespecial #32                 // Method com/polymorphism/Tricycl
e."<init>":()V
        31: astore_3
        32: aload_3
        33: invokestatic  #25                 // Method ride:(Lcom/polymorphism/
Cycle;)V
        36: new           #33                 // class com/polymorphism/Unicycle

        39: dup
        40: invokespecial #35                 // Method com/polymorphism/Unicycl
e."<init>":()V
        43: astore        4
        45: aload         4
        47: invokestatic  #25                 // Method ride:(Lcom/polymorphism/
Cycle;)V
        50: return

我认为@JBNizet已经在评论中找到了解决方案(我的猜测结果是错误的)。但既然他没有把它作为答案发布,我就这么做:

main
方法不应该显示任何动态行为,因为它总是调用单个方法
TestRide.ride(循环c)
。没有其他可能的方法,所以没有什么要弄清楚的


动态方法调用位于该方法的内部
TestRide.ride(循环c)
。现在您已经发布了该代码,实际上我们看到了一个使用
invokevirtual
的动态方法分派。所以,毕竟,没有什么意外。动态方法调度就在那里。

首先
invokedynamic
用于Java8lambda和非Java代码,所以您可以忽略这一点

除此之外,还有四条调用指令(
invokespecial
invokestatic
invokevirtual
,以及
invokeinterface
)。您可以在JVM规范中看到精确的语义,但底线是
invokevirtual
invokeinterface
都是虚拟方法调用,也就是说,在运行时根据目标的conrete类型选择调用的实际方法

代码中唯一的虚拟调用是TestRide.ride。列出的目标是
循环。行驶:()V
。但是,由于它是一个虚拟调用,JVM将在运行时检查第一个参数的实际类型,并调用该方法的最派生版本

这类似于C++中的虚拟方法调用,除了JVM和JIT编译的抽象允许潜在的更优化的实现。


还要注意,这不能与方法重载混淆,方法重载是编译时多态性的一种形式。对于重载方法,编译器根据参数的编译时类型选择要调用的方法。

可能您的测试用例太简单了。如果Java编译器在编译时知道将调用什么方法,则不需要动态执行任何操作的字节码。
invokedynamic
是在JVM字节码中引入的,用于动态方法查找,而不遵循Java的方法查找。为什么Java编译器会使用它?使用
invokevirtual
invokeinterface
完成虚拟方法调度。请注意,如果Java编译器愿意,它当然可以免费使用
invokedynamic
。Java语言规范没有说明Java必须如何编译,它甚至没有说它必须被编译,它也可以被解释。您正在发布main()的字节码,其中有0个多态方法调用:只有构造函数调用和静态方法调用(to
ride()
)。多态方法调用是在
TestRide.ride()
@mastov no中进行的。它对静态方法ride()进行了4次invokestatic调用,以一个循环作为参数。基类没有发现任何东西。invokevirtual有一个Cycle对象,在该对象上调用ride()方法作为堆栈上的第一个参数。它获取该Cycle对象的具体类(例如,获取BiCycle或TriCycle)。然后按照中描述的过程在该具体类中查找ride()方法