0

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

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

假设类Carextends 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"); 
    }
}

正如预期的那样,这段代码的输出是:“汽车类”

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

为什么?

4

5 回答 5

1

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

于 2013-10-04T19:34:28.897 回答
1

删除的问题start()Vehicle多态性有关。在Vehicle中,如果您start()在此处定义,那么这就是说所有Vehicles,甚至是子类,都具有该方法。

如果您从 中删除start()Vehicle()则无法保证任何方法Vehicle都具有start()方法,即使我们知道它Car确实具有start()。如果有一个HorselessCarriage扩展Vehicle但没有定义的类start()怎么办?那么,就没有start()办法了。因此,如果没有onstart()方法Vehicle,则不能调用变量。start()Vehicle

能够调用start()a的全部意义Vehicle在于确保任何 Vehicle实现都有start()可以调用的方法。

更新

JVM 采用对象的运行时类型并查找与方法调用的签名匹配的方法。如果没有找到,它会沿着继承树走到超类并在那里寻找方法。

JLS 第 15.12.4.4 节提供了更多详细信息:

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

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

如果调用方式是super或者interface,那么this就是被调用的方法,过程终止。

如果调用模式是虚拟的,并且 S 中的声明覆盖(第 8.4.8.1 节)Xm,那么 S 中声明的方法就是要调用的方法,并且过程终止。

如果调用方式是虚拟的,并且 S 中的声明没有覆盖 Xm,而且 Xm 被声明为抽象,则抛出 AbstractMethodError。

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

在这里,S似乎是对象的运行时类型。

于 2013-10-04T19:33:02.693 回答
0

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

于 2013-10-04T19:33:59.977 回答
0

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

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

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

于 2013-10-04T19:35:29.537 回答
0

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

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

此方法允许您传入一个具体的Car或一个具体的Vehicle

让我们假设 Java 允许您编译此代码。 如果Java 允许您在Vehicle没有start()方法的情况下执行此操作,那么您必须在运行时发现错误。但是 Java 通过在编译时通知您有错误来为您节省一些时间。

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

于 2013-10-04T19:37:57.483 回答