一、向上转型(Upcasting)
我们先来看一个演奏乐器的代码:
public enum Note {
MIDDLE_C,
C_SHARP,
B_FLAT;
}
public class Instrument {
public void play(Note n) {
System.out.println("Instrument plays: " + n);
}
}
public class Wind extends Instrument {
@Override
public void play(Note n) {
System.out.println("Wind plays: " + n);
}
}
public class Music {
public static void main(String[] args) {
tune(new Wind()); //upcasting
}
static void tune(Instrument instrument) {
instrument.play(Note.C_SHARP);
}
}
在Music
的main()
中,我们调用了tune()·
。tune()
所需要的参数是一个Instrument
,我们传给它一个Wind
,Wind
是Instrument
的一个子类,因此称这种赋值方式为向上转型。
向上转型是被允许的,因为子类继承父类,子类拥有父类接口所有的方法,使用父类的引用来调用方法,不会超过子类所能执行的范围。
问题来了,在上面例子中的向上转型:Instrument instrument = new Wind()
,虽然对象是Wind
,但是引用是instrument
,为什么调用方法的时候,能准确调用到Wind.play()
,而不是Instrument.play()
呢?
二、静态绑定和动态绑定
- 在编译时期,编译器会把方法名和方法体进行绑定,这个行为称为
静态绑定
。 - 在运行时期,Java会通过某种运行机制,将引用的方法名与正确的方法体绑定,这个行为称为
动态绑定
。
注意:动态绑定
是根据父类的接接口进行绑定的,即在子类中存在而在父类中不存在的方法,我们无法通过父类的引用进行调用。
public class DynamicBinding {
public static void main(String[] args) {
Useful u = new MoreUseful();
u.use();
// u.moreUseful(); cannot resvoled method moreUseful()
}
}
class Useful {
public void use() {
System.out.println("Useful.use()");
}
}
class MoreUseful extends Useful {
@Override
public void use() {
System.out.println("MoreUseful.use()");
}
public void moreUseful() {
System.out.println("moreUseful()");
}
}
三、使用多态来实现演奏乐器代码
通过上面的解释,我们就大概能知道多态的原理了,现在,我们使用多态来实现演奏乐器的代码,在已有代码的基础上加上:
public class Brass extends Instrument {
@Override
public void play(Note n) {
System.out.println("Brass play: " + n);
}
}
public class Music {
public static void main(String[] args) {
tune(new Wind());
tune(new Brass());
}
static void tune(Instrument instrument) {
instrument.play(Note.C_SHARP);
}
}
我们不需要修改原有的tune()
方法,就可以让它演奏新增的乐器Brass
。
参考:Thinking In Java(4th edition)