7.1 上溯造型

在第6章,大家已知道可将一个对象作为它自己的类型使用,或者作为它的基础类型的一个对象使用。取得一个对象句柄,并将其作为基础类型句柄使用的行为就叫作“上溯造型”——因为继承树的画法是基础类位于最上方。

但这样做也会遇到一个问题,如下例所示(若执行这个程序遇到麻烦,请参考第3章的3.1.2小节“赋值”):

//: Music.java

// Inheritance & upcasting

package c07;

class Note {

private int value;

private Note(int val) { value = val; }

public static final Note

middleC = new Note(0),

cSharp = new Note(1),

cFlat = new Note(2);

} // Etc.

class Instrument {

public void play(Note n) {

System.out.println("Instrument.play()");

}

}

// Wind objects are instruments

// because they have the same interface:

class Wind extends Instrument {

// Redefine interface method:

public void play(Note n) {

System.out.println("Wind.play()");

}

}

public class Music {

public static void tune(Instrument i) {

// …

i.play(Note.middleC);

}

public static void main(String[] args) {

Wind flute = new Wind();

tune(flute); // Upcasting

}

} ///:~

其中,方法Music.tune()接收一个Instrument句柄,同时也接收从Instrument衍生出来的所有东西。当一个Wind句柄传递给tune()的时候,就会出现这种情况。此时没有造型的必要。这样做是可以接受的;Instrument里的接口必须存在于Wind中,因为Wind是从Instrument里继承得到的。从Wind向Instrument的上溯造型可能“缩小”那个接口,但不可能把它变得比Instrument的完整接口还要小。

7.1.1 为什么要上溯造型

这个程序看起来也许显得有些奇怪。为什么所有人都应该有意忘记一个对象的类型呢?进行上溯造型时,就可能产生这方面的疑惑。而且如果让tune()简单地取得一个Wind句柄,将其作为自己的自变量使用,似乎会更加简单、直观得多。但要注意:假如那样做,就需为系统内Instrument的每种类型写一个全新的tune()。假设按照前面的推论,加入Stringed(弦乐)和Brass(铜管)这两种Instrument(乐器):

//: Music2.java

// Overloading instead of upcasting

class Note2 {

private int value;

private Note2(int val) { value = val; }

public static final Note2

middleC = new Note2(0),

cSharp = new Note2(1),

cFlat = new Note2(2);

} // Etc.

class Instrument2 {

public void play(Note2 n) {

System.out.println("Instrument2.play()");

}

}

class Wind2 extends Instrument2 {

public void play(Note2 n) {

System.out.println("Wind2.play()");

}

}

class Stringed2 extends Instrument2 {

public void play(Note2 n) {

System.out.println("Stringed2.play()");

}

}

class Brass2 extends Instrument2 {

public void play(Note2 n) {

System.out.println("Brass2.play()");

}

}

public class Music2 {

public static void tune(Wind2 i) {

i.play(Note2.middleC);

}

public static void tune(Stringed2 i) {

i.play(Note2.middleC);

}

public static void tune(Brass2 i) {

i.play(Note2.middleC);

}

public static void main(String[] args) {

Wind2 flute = new Wind2();

Stringed2 violin = new Stringed2();

Brass2 frenchHorn = new Brass2();

tune(flute); // No upcasting

tune(violin);

tune(frenchHorn);

}

} ///:~

这样做当然行得通,但却存在一个极大的弊端:必须为每种新增的Instrument2类编写与类紧密相关的方法。这意味着第一次就要求多得多的编程量。以后,假如想添加一个象tune()那样的新方法或者为Instrument添加一个新类型,仍然需要进行大量编码工作。此外,即使忘记对自己的某个方法进行过载设置,编译器也不会提示任何错误。这样一来,类型的整个操作过程就显得极难管理,有失控的危险。

但假如只写一个方法,将基础类作为自变量或参数使用,而不是使用那些特定的衍生类,岂不是会简单得多?也就是说,如果我们能不顾衍生类,只让自己的代码与基础类打交道,那么省下的工作量将是难以估计的。

这正是“多形性”大显身手的地方。然而,大多数程序员(特别是有程序化编程背景的)对于多形性的工作原理仍然显得有些生疏。