【问题标题】:Misunderstanding Dynamic Binding对动态绑定的误解
【发布时间】:2013-10-04 19:27:44
【问题描述】:

根据我对动态绑定的了解,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 实现。但是,它不这样做。

为什么?

【问题讨论】:

  • 因为该方法不存在。你会得到一个NoSuchMethodError
  • 恕我直言,如果您删除 start 函数并执行 Vehicle v = new Car() 这将是编译器错误。由于编译器会检查 LHS 对象以验证调用的函数是否存在于其中。
  • @VusP 这取决于他修改了 .class 还是 .java。但是,是的,更有可能出现编译错误。
  • @tieTYT 也许我误解了“如果我从 Vehicle 类中取出 start 方法,请完全删除它”。他这是什么意思?
  • @VusP 我认为这是模棱两可的。我们都以不同的方式解释它:)但这并不妨碍我们回答这个问题。

标签: java inheritance dynamic binding


【解决方案1】:

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

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

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

更新

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

更多详情在JLS, Section 15.12.4.4:

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

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

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

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

如果调用方式是virtual,并且S中的声明没有 覆盖 X.m,而且 X.m 被声明为抽象的,然后是 抛出 AbstractMethodError。

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

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

【讨论】:

  • 这是有道理的,我什至在写这个问题之前就怀疑了。但是我不得不问,动态绑定究竟是如何工作的?在决定调用什么方法时,JVM从哪里开始?
  • @rgettman 感谢您的确认。这就是我认为它有效但尚未查找的方式!
【解决方案2】:

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

【讨论】:

  • 我不太明白。 JVM 在这个实例中选择的端点是什么?另外,镜子是什么意思?
  • 动态链接必须有一个起点,并在virtual table 中找到对方法的适当引用,这样如果您尝试访问此变量中不存在的方法,只需JVM没有通过编译面,因为不是这个变量没有这个方法。
【解决方案3】:

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

【讨论】:

  • 我将以与 rgettman 评论相同的方式回复。这是有道理的,我预料到了这一点,但是在运行时,JVM 在决定运行哪个方法实现时从哪个类开始?
  • 在运行时,我认为它从实际对象的类开始。所以在这种情况下,它将从查看 Car 开始。
【解决方案4】:

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

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

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

【讨论】:

    【解决方案5】:

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

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

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

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

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2010-09-20
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-10-29
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多