0

我试图在更深层次上理解动态/静态绑定,我可以说经过大量阅读和搜索后,我对某些事情感到非常困惑。

好吧,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 的方法?这是编译器的设计问题还是我遗漏了什么?

4

3 回答 3

3

原因是编译器不知道该方法属于哪个类,对吧?

实际上,没有。编译器不想知道目标对象的具体类型。这允许现在编译的代码在未来使用甚至还不存在的类。

作为最明显的例子,考虑一个 JDK 方法,如Collections.sort(List). List您可以将刚刚创建的实现传递给它。您不希望通知 Oracle 您已完成此操作,并希望他们将其包含在“静态支持”列表类型的列表中。

于 2016-11-04T11:17:48.427 回答
3

动态绑定是绝对必要的。例如,假设您有这样的事情:

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 类层次结构中的可重写方法,用于动态调用该方法的字节码是生成的。

于 2016-11-04T11:23:18.097 回答
2

目前尚不清楚您的问题实际上是基于什么。

当你有表格的代码时

 Dog d = new Dog();
 d.eat();

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

对于调用,有几种可能的情况

  • Dog可能会声明一个方法eat(),该方法会覆盖在其超类中具有相同签名的方法Animal,就像在您的示例中一样
  • Dog可能声明一个eat()不覆盖另一个方法的方法
  • Dog可能不声明匹配方法,但从其超类或实现的接口继承匹配方法

请注意,它完全不相关,适用于哪种情况。如果调用有效,它将被编译为 的调用Dog.eat(),无论应用哪种情况,因为d在其eat()上调用的正式静态类型是Dog

与实际场景无关也意味着在运行时,您可能有不同版本的 class Dog,适用于另一个场景,而不会破坏兼容性。


如果你写了这将是一个不同的画面

Animal a = new Dog();
a.eat();

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


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

因此,也许,您编译了上面两个示例之一,稍后,您正在使用较新的库版本运行代码,其中类型层次结构是Animal>>Carnivore并且没有实现Dog,因为最具体的自然位置实施是. 在那种环境中,您的旧代码仍将运行并做正确的事情,没有问题。Dogeat()Carnivore.eat()

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

于 2016-11-04T16:04:14.743 回答