以下内容有选择地摘自《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() 方法。于是书中所说的令人迷惑的地方在哪里?还有,引用中的那句话要怎么理解?
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号
试想一下,如果该class被其它代码import,那么tune方法接受的参数的类型还一定是Wind吗?
所以,编译器其实不能确定tune方法接受的参数为Wind类型,只能确定它是代码中写的Instrument类型。JVM提供了invokevirtual指令,用于实现这个polymorphic调用。如果编译器换成invokespecial Wind.play,那这段代码的语义就不一样了。
你可能会想,如果将tune方法标记成private,是不是编译器就能够分析出tune方法始终只接受一个Wind类型的参数,期望它能将其优化成invokespecial,这样效率更高呢?其实程序员不用关心这个的,JVM会对invokevirtual自动作优化,不会每次调用都去查找是调用子类还是父类方法的。
Java中属性绑定到类型,方法绑定到对象.
编译时期,系统并不清楚变量是哪个类的实例。具体调用哪个类的方法 运行时才能决定。
实际上有些情况下,编译器是可以确定的,比如这个例子
但是为了安全和一致性,索性就交给了动态绑定。具体可以参考Java中的静态绑定和动态绑定