按键盘上方向键 ← 或 → 可快速上下翻页,按键盘上的 Enter 键可回到本书目录页,按键盘上方向键 ↑ 可回到本页顶部!
————未阅读完?加入书签已便下次继续阅读!
类继承新的数据类型,从而新添一些功能。如果是为了适应新类的要求,那么对基础类接口进行操纵的方法
根本不需要改变,
对于乐器例子,假设我们在基础类里加入更多的方法,以及一系列新类,那么会出现什么情况呢?下面是示
意图:
165
…………………………………………………………Page 167……………………………………………………………
所有这些新类都能与老类——tune()默契地工作,毋需对tune()作任何调整。即使 tune()位于一个独立的文
件里,而将新方法添加到 Instrument 的接口,tune()也能正确地工作,不需要重新编译。下面这个程序是对
上述示意图的具体实现:
//: Music3。java
// An extensible program
import java。util。*;
class Instrument3 {
public void play() {
System。out。println(〃Instrument3。play()〃);
}
public String what() {
return 〃Instrument3〃;
}
public void adjust() {}
}
class Wind3 extends Instrument3 {
public void play() {
System。out。println(〃Wind3。play()〃);
}
public String what() { return 〃Wind3〃; }
public void adjust() {}
}
class Percussion3 extends Instrument3 {
public void play() {
166
…………………………………………………………Page 168……………………………………………………………
System。out。println(〃Percussion3。play()〃);
}
public String what() { return 〃Percussion3〃; }
public void adjust() {}
}
class Stringed3 extends Instrument3 {
public void play() {
System。out。println(〃Stringed3。play()〃);
}
public String what() { return 〃Stringed3〃; }
public void adjust() {}
}
class Brass3 extends Wind3 {
public void play() {
System。out。println(〃Brass3。play()〃);
}
public void adjust() {
System。out。println(〃Brass3。adjust()〃);
}
}
class Woodwind3 extends Wind3 {
public void play() {
System。out。println(〃Woodwind3。play()〃);
}
public String what() { return 〃Woodwind3〃; }
}
public class Music3 {
// Doesn't care about type; so new types
// added to the system still work right:
static void tune(Instrument3 i) {
// 。。。
i。play();
}
static void tuneAll(Instrument3'' e) {
for(int i = 0; i 《 e。length; i++)
tune(e'i');
}
public static void main(String'' args) {
Instrument3'' orchestra = new Instrument3'5';
int i = 0;
// Upcasting during addition to the array:
orchestra'i++' = new Wind3();
orchestra'i++' = new Percussion3();
orchestra'i++' = new Stringed3();
orchestra'i++' = new Brass3();
orchestra'i++' = new Woodwind3();
tuneAll(orchestra);
}
167
…………………………………………………………Page 169……………………………………………………………
} ///:~
新方法是what()和adjust() 。前者返回一个String 句柄,同时返回对那个类的说明;后者使我们能对每种
乐器进行调整。
在main()中,当我们将某样东西置入Instrument3数组时,就会自动上溯造型到 Instrument3。
可以看到,在围绕 tune()方法的其他所有代码都发生变化的同时,tune()方法却丝毫不受它们的影响,依然
故我地正常工作。这正是利用多形性希望达到的目标。我们对代码进行修改后,不会对程序中不应受到影响
的部分造成影响。此外,我们认为多形性是一种至关重要的技术,它允许程序员“将发生改变的东西同没有
发生改变的东西区分开”。
7。3 覆盖与过载
现在让我们用不同的眼光来看看本章的头一个例子。在下面这个程序中,方法play()的接口会在被覆盖的过
程中发生变化。这意味着我们实际并没有“覆盖”方法,而是使其“过载”。编译器允许我们对方法进行过
载处理,使其不报告出错。但这种行为可能并不是我们所希望的。下面是这个例子:
//: WindError。java
// Accidentally changing the interface
class NoteX {
public static final int
MIDDLE_C = 0; C_SHARP = 1; C_FLAT = 2;
}
class InstrumentX {
public void play(int NoteX) {
System。out。println(〃InstrumentX。play()〃);
}
}
class WindX extends InstrumentX {
// OOPS! Changes the method interface:
public void play(NoteX n) {
System。out。println(〃WindX。play(NoteX n)〃);
}
}
public class WindError {
public static void tune(InstrumentX i) {
// 。。。
i。play(NoteX。MIDDLE_C);
}
public static void main(String'' args) {
WindX flute = new WindX();
tune(flute); // Not the desired behavior!
}
} ///:~
这里还向大家引入了另一个易于混淆的概念。在 InstrumentX 中,play()方法采用了一个 int (整数)数
值,它的标识符是NoteX。也就是说,即使NoteX 是一个类名,也可以把它作为一个标识符使用,编译器不
会报告出错。但在WindX 中,play()采用一个NoteX 句柄,它有一个标识符 n。即便我们使用“play(NoteX
NoteX)”,编译器也不会报告错误。这样一来,看起来就象是程序员有意覆盖play()的功能,但对方法的类
型定义却稍微有些不确切。然而,编译器此时假定的是程序员有意进行“过载”,而非“覆盖”。请仔细体
168
…………………………………………………………Page 170……………………………………………………………
会这两个术语的区别。“过载”是指同一样东西在不同的地方具有多种含义;而“覆盖”是指它随时随地都
只有一种含义,只是原先的含义完全被后来的含义取代了。请注意如果遵守标准的Java 命名规范,自变量标
识符就应该是noteX,这样可把它与类名区分开。
在 tune 中,“InstrumentX i ”会发出play()消息,同时将某个 NoteX 成员作为自变量使用(MIDDLE_C)。
由于NoteX 包含了 int 定义,过载的play()方法的 int 版本会得到调用。同时由于它尚未被“覆盖”,所以
会使用基础类版本。
输出是:
InstrumentX。play()
7。4 抽象类和方法
在我们所有乐器(Instrument)例子中,基础类 Instrument 内的方法都肯定是“伪”方法。若去调用这些方
法,就会出现错误。那是由于 Instrument 的意图是为从它衍生出去的所有类都创建一个通用接口。
之所以要�