按键盘上方向键 ← 或 → 可快速上下翻页,按键盘上的 Enter 键可回到本书目录页,按键盘上方向键 ↑ 可回到本页顶部!
————未阅读完?加入书签已便下次继续阅读!
长”。
通过合并各种特征与行为,封装技术可创建出新的数据类型。通过对具体实施细节的隐藏,可将接口与实施
细节分离,使所有细节成为“private”(私有)。这种组织方式使那些有程序化编程背景人感觉颇为舒适。
但多形性却涉及对“类型”的分解。通过上一章的学习,大家已知道通过继承可将一个对象当作它自己的类
型或者它自己的基础类型对待。这种能力是十分重要的,因为多个类型(从相同的基础类型中衍生出来)可
被当作同一种类型对待。而且只需一段代码,即可对所有不同的类型进行同样的处理。利用具有多形性的方
法调用,一种类型可将自己与另一种相似的类型区分开,只要它们都是从相同的基础类型中衍生出来的。这
种区分是通过各种方法在行为上的差异实现的,可通过基础类实现对那些方法的调用。
在这一章中,大家要由浅入深地学习有关多形性的问题(也叫作动态绑定、推迟绑定或者运行期绑定)。同
时举一些简单的例子,其中所有无关的部分都已剥除,只保留与多形性有关的代码。
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()〃);
160
…………………………………………………………Page 162……………………………………………………………
}
}
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()〃);
161
…………………………………………………………Page 163……………………………………………………………
}
}
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添加一个新类型,仍然需要进行大量编码工作。此外,即使忘记对自己的某个方法进行过载设
置,编译器也不会提示任何错误。这样一来,类型的整个操作过程就显得极难管理,有失控的危险。
但假如只写一个方法,将基础类作为自变量或参数使用,而不是使用那些特定的衍生类,岂不是会简单得
多?也就是说,如果我们能不顾衍生类,只让自己的代码与基础类打交道,那么省下的工作量将是难以估计
的。
这正是“多形性”大显身手的地方。然而,大多数程序员(特别是有程序化编程背景的)对于多形性的工作
原理仍然显得有些生疏。
7。2 深入理解
对于Music。java 的困难性,可通过运行程序加以体会。输出是Wind。play()。这当然是我们希望的输出,但
它看起来似乎并不愿按我们的希望行事。请观察一下tune()方法:
public static void tune(Instrument i) {
// 。。。
i。play(Note。middleC);
}
它接收 Instrument 句柄。所以在这种情况下,编译器怎样才能知道 Instrument句柄指向的是一个 Wind ,而
不是一个Brass 或Stringed 呢?编译器无从得知。为了深入了理解这个问题,我们有必要探讨一下“绑定”
这个主题。
162
…………………………………………………………Page 164……………………………………………………………
7。2。1 方法调用的绑定
将一个方法调用同一个方法主体连接到一起就称为�