误解动态绑定

时间:2013-10-04 19:27:45

标签: java inheritance dynamic binding

根据我对动态绑定的理解,JVM在运行时查看对象的实际类型,并在该类中搜索实现,并在继承级别上向上运行。

例如,如果我们有:Vehicle v = new Car();

假设类Car扩展Vehicle,我们可以看到引用变量类型是Vehicle,对象类型是Car。

如果我们要说:v.start()

JVM首先在Car类中查找start方法实现,然后在Vehicle类中查找。

这个代码的一个例子是:

public class scratch{
    public static void main(String [] args){
        Vehicle v = new Car(); 
        v.start(); 
    }
}

class Vehicle{
    public void start(){
        System.out.println("Vehicle class"); 
    }
}

class Car extends Vehicle{
    public void start(){
        System.out.println("Car class"); 
    }
}

此代码的输出正如预期的那样:“Car class”

这是我的问题:如果我从类Vehicle中取出start方法,完全删除它,程序将不再运行。根据我对动态绑定的理解,JVM仍然应该查看对象的实际类型(在本例中为Car),并且仍然运行start方法的car实现。但是,它没有这样做。

为什么呢?

5 个答案:

答案 0 :(得分:1)

start()删除Vehicle的问题与多态性有关。在Vehicle中,如果您在此处定义start(),则表示所有Vehicle,甚至是子类都具有该方法。

如果您从start()移除Vehicle(),则无法保证任何Vehicle都有start()方法,即使我们知道它是{{1}确实有Car。如果start()类扩展HorselessCarriage但未定义Vehicle,该怎么办?然后,没有start()方法。因此,如果start()上没有start()方法,则无法在Vehicle变量上调用start()

能够在Vehicle上致电start()的重点是确保任何 Vehicle实施都采用Vehicle方法调用

<强>更新

JVM获取对象的运行时类型,并查找与方法调用的签名匹配的方法。如果没有找到,它会将继承树向上移动到超类并在那里查找方法。

JLS, Section 15.12.4.4

中提供了更多详细信息
  

设X是方法的目标引用的编译时类型   调用。然后:

     

如果类S包含一个名为m的非抽象方法的声明   具有相同的描述符(相同数量的参数,相同   方法所需的参数类型和相同的返回类型   在编译时确定的调用(第15.12.3节),然后:

     

如果调用模式是super或interface,那么这就是方法   被调用,程序终止。

     

如果调用模式为虚拟,则S中的声明将覆盖   (§8.4.8.1)X.m,那么在S中声明的方法就是方法   调用,程序终止。

     

如果调用模式是虚拟的,则S中的声明不是   覆盖X.m,而且X.m被声明为abstract,然后是   抛出AbstractMethodError。

     

否则,如果S有一个超类,那么相同的查找过程就是   使用S的直接超类代替S递归地执行;   要调用的方法是递归调用的结果   这个查找程序。

此处,start()似乎是对象的运行时类型。

答案 1 :(得分:1)

简而言之,JVM需要一个端点来开始搜索start方法的引用,而不管对象类型是否有您想要调用的方法,JVM需要镜像,以确保您尝试调用现有方法。

答案 2 :(得分:0)

当您在Vehicle类中使用start()方法时,Car正在覆盖该方法。从Vehicle中删除start()方法时,您不再覆盖该方法。所以调用v.start()没有方法可以调用。这就是你应该使用@Override的原因,以便在代码中清楚地知道发生了什么。为了在Car中没有start()方法的情况下调用Car上的start(),你首先必须将车辆投射到Car类。

答案 3 :(得分:0)

但是一旦删除该方法,Vehicle就不再具有任何“启动”功能:它是Vehicle类中的未知方法,您通过Vehicle引用访问它。为了让java做你想做的事,你可以做到

abstract class Vehicle
{
  public abstract void start();
}

您的代码应该再次运行,因为现在可以保证Vehicle的所有子代都有一个启动方法。但是正如你的例子所示,一旦删除了start方法,就不能保证某些先前的语句没有创建其他一些Vehicle后代,例如没有start方法的Motorcycle并将其分配给你的v引用。

答案 4 :(得分:0)

我认为解决问题的一个简单方法是引入一种方法。以下是该方法的定义方式:

public void callStart(Vehicle vehicle) {
  vehicle.start();
}

此方法可让您传递具体的Car或具体的Vehicle

让我们假装Java允许你编译这段代码。 如果 Java允许您在没有Vehicle方法的情况下为start()执行此操作,那么您必须在运行时发现错误。但是,当你编译错误时,Java会让你知道一些时间。

这与Javascript等动态语言不同。如果这是JavaScript,你可以传递一个具体的Vehicle,然后你必须在运行时发现你的错误。另一个区别是,在JavaScript中,您可以传递一个具体的Car,它可以正常工作。这称为duck typing,是Java没有的功能。