根据我对动态绑定的理解,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实现。但是,它没有这样做。
为什么呢?
答案 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获取对象的运行时类型,并查找与方法调用的签名匹配的方法。如果没有找到,它会将继承树向上移动到超类并在那里查找方法。
中提供了更多详细信息设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没有的功能。