关于Java动态绑定的疑惑

以下内容有选择地摘自《Java编程思想》:

enum Note {
    MIDDLE_C, C_SHARP, B_FLAT
}

class Instrument {
    public void play(Note n) {
        System.out.println("Instrument.play() " + n);
    }
}

class Wind extends Instrument {
    @Override
    public void play(Note n) {
        System.out.println("Wind.play() " + n);
    }
}

class Brass extends Instrument {
    @Override
    public void play(Note n) {
        System.out.println("Brass.play() " + n);
    }
}

public class Music {
    public static void tune(Instrument i) {
        i.play(Note.B_FLAT);
    }

    public static void main(String[] args) {
        Wind flute = new Wind();
        tune(flute);
    }
}

之后,书中提到:

请观察一下 tune() 方法,它接受一个 Instrument 引用。那么在这种情况下,编译器怎样才能知道这个 Instrument 引用指向的是 Wind 对象,而不是 Brass 对象呢?实际上,编译器无法得知。
上述程序之所以令人迷惑,主要是因为前期绑定。因为,当编译器只有一个 Instrument 引用时,它无法知道究竟调用哪个方法才对。(这句话要怎么理解?)

所以,我的疑问是:既然 tune() 内接的是 Wind 对象 flute,那么它就应该知道调用 Wind.play() 方法,而不是调用 Instrument.play() 方法或 Brass.play() 方法。如果 Wind 没有覆盖 play() 方法,那么最终应该调用基类 Instrument.play() 方法。于是书中所说的令人迷惑的地方在哪里?还有,引用中的那句话要怎么理解?

阅读 4.2k
3 个回答

试想一下,如果该class被其它代码import,那么tune方法接受的参数的类型还一定是Wind吗?
所以,编译器其实不能确定tune方法接受的参数为Wind类型,只能确定它是代码中写的Instrument类型。JVM提供了invokevirtual指令,用于实现这个polymorphic调用。如果编译器换成invokespecial Wind.play,那这段代码的语义就不一样了。

你可能会想,如果将tune方法标记成private,是不是编译器就能够分析出tune方法始终只接受一个Wind类型的参数,期望它能将其优化成invokespecial,这样效率更高呢?其实程序员不用关心这个的,JVM会对invokevirtual自动作优化,不会每次调用都去查找是调用子类还是父类方法的。

Java中属性绑定到类型,方法绑定到对象.

编译时期,系统并不清楚变量是哪个类的实例。具体调用哪个类的方法 运行时才能决定。

实际上有些情况下,编译器是可以确定的,比如这个例子

public static void main(String[] args) {
      String str = new String();
      final Caller callerSub = new SubCaller();
      callerSub.call(str);
}

但是为了安全和一致性,索性就交给了动态绑定。具体可以参考Java中的静态绑定和动态绑定

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题