Java动态绑定:编译器无法区分重写方法的原因

Java动态绑定:编译器无法区分重写方法的原因,java,jvm,javac,dynamic-binding,dynamic-dispatch,Java,Jvm,Javac,Dynamic Binding,Dynamic Dispatch,我试图从更深的层面理解动态/静态绑定,我可以说,经过大量的阅读和搜索,我真的对某些东西感到困惑 java对重写的方法使用动态绑定,原因是编译器不知道该方法属于哪个类,对吗? 例如: public class Animal{ void eat(){ } class Dog extends Animal{ @Override void eat(){} } public static void main(String[] args[]){ Dog

我试图从更深的层面理解动态/静态绑定,我可以说,经过大量的阅读和搜索,我真的对某些东西感到困惑

java对重写的方法使用动态绑定,原因是编译器不知道该方法属于哪个类,对吗? 例如:

public class Animal{
       void eat(){
}

class Dog extends Animal{
       @Override
       void eat(){}
}

public static void main(String[] args[]){
     Dog d = new Dog();
     d.eat();
}
我的问题是,为什么编译器不知道代码引用了Dog类eat()方法,即使d引用被声明为Dog类,并且Dog的构造函数被用于在运行时创建实例? 对象将在运行时创建,但为什么编译器不理解代码引用了Dog的方法?这是编译器的设计问题还是我遗漏了什么

这是因为编译器不知道该方法属于哪个类,对吗

实际上,不是。编译器不想知道目标对象的特定类型。这使得现在编译的代码将来可以处理甚至还不存在的类


作为最明显的例子,考虑一个JDK方法,如<代码>集合。排序(列表)< /代码>。您可以将刚刚创建的

List
的实现传递给它。您不希望必须通知Oracle您这样做了,并希望他们将其包括在“静态支持”列表类型中。

动态绑定是绝对必要的。例如,假设您有如下内容:

Animal a;
String kind = askTheUser();
if (kind.equals("Dog") {
    a = new Dog();
}
else {
    a = new Cat();
}
a.eat();
显然,在这里,编译器在编译时不能知道
a
是一只狗。它可能是一只猫。所以它必须使用动态绑定

现在您可以说,在您的示例中,编译器可以知道并可以进行优化。然而,Java并不是这样设计的。由于JIT编译器,大多数优化都发生在运行时。JIT编译器(可能)能够在运行时进行这种优化,而静态编译器则无法做到这一点。因此,Java决定简化静态编译器和字节码,并将其优化工作集中在JIT编译器中


因此,当编译器编译时,它只关心
d.eat()
d
是Dog类型,
eat()
是Dog类层次结构中存在的可重写方法,用于动态调用此方法的字节码是生成的

不清楚你的问题的真正依据是什么

当你有了表格的代码

 Dog d = new Dog();
 d.eat();
d
的静态类型是
Dog
,因此,在检查调用是否正确后,编译器将
Dog.eat()
的调用编码到类文件中

对于调用,有几种可能的场景

  • Dog
    可能会声明一个方法
    eat()
    ,该方法覆盖其超类
    Animal
    中具有相同签名的方法,如您的示例中所示
  • Dog
    可能会声明一个不重写另一个方法的方法
    eat()
  • Dog
    可能不会声明匹配方法,但会从其超类或实现的接口继承匹配方法
请注意,这与应用哪种场景完全无关。如果调用有效,它将被编译为调用
Dog.eat()
,无论应用哪种情况,因为调用
eat()
d
的形式静态类型是
Dog

对实际场景的不可知性还意味着在运行时,您可能会有另一个场景适用的类
Dog
的不同版本,而不会破坏兼容性


如果你写了,那将是一幅不同的画

Animal a = new Dog();
a.eat();
现在
a
的形式类型是
Animal
,编译器将检查
Animal
是否包含
eat()
的声明,无论它是否在
Dog
中被覆盖。然后,此调用将在字节码中编码为targeting
Animal.eat()
,即使编译器可以推断
a
实际上是对
Dog
实例的引用。编译器只是遵循形式规则。这意味着,如果运行时版本的
Animal
缺少
eat()
方法,即使
Dog
有一个方法,该代码也不会工作


这意味着删除基类中的方法将是一个危险的更改,但您始终可以通过添加更抽象的基类来重构代码,并在类层次结构中向上移动方法,而不会影响与现有代码的兼容性。这是Java设计者的目标之一

因此,您可能编译了上面两个示例中的一个,然后使用更新的库版本运行代码,其中类型层次结构是
Animal
Carnivore
Dog
Dog
没有
eat()的实现,因为最具体实现的自然位置是
Carnivore.eat()
。在这种环境中,您的旧代码仍将运行并执行正确的操作,不会出现问题


进一步注意,即使您重新编译旧代码而不做任何更改,但使用较新的库,它将与旧库版本保持兼容,因为在您的代码中,您永远不会引用新的
Carnivore
类型,编译器将使用您在代码中使用的形式类型,
Animal
Dog
,根据上述形式规则,不将
Dog
Carnivore
继承方法
eat()
的事实记录到编译代码中。这里没有意外。

谢谢您的时间。您所说的“静态支持”实际上是什么意思?假设没有动态调度,编译器将必须知道您的类型。这意味着甲骨文将