友情提示:如果本网页打开太慢或显示不完整,请尝试鼠标右键“刷新”本网页!阅读过程发现任何错误请告诉我们,谢谢!! 报告错误
一世书城 返回本书目录 我的书架 我的书签 TXT全本下载 进入书吧 加入书签

Java编程思想第4版[中文版](PDF格式)-第63章

按键盘上方向键 ← 或 → 可快速上下翻页,按键盘上的 Enter 键可回到本书目录页,按键盘上方向键 ↑ 可回到本页顶部!
————未阅读完?加入书签已便下次继续阅读!




    // Fill up the array with shapes:  

    for(int i = 0; i 《 s。length; i++)  

      s'i' = randShape();  



                                                                                             164 


…………………………………………………………Page 166……………………………………………………………

    // Make polymorphic method calls:  

    for(int i = 0; i 《 s。length; i++)  

      s'i'。draw();  

  }  

} ///:~  

  

针对从 Shape 衍生出来的所有东西,Shape 建立了一个通用接口——也就是说,所有(几何)形状都可以描 

绘和删除。衍生类覆盖了这些定义,为每种特殊类型的几何形状都提供了独一无二的行为。  

在主类 Shapes 里,包含了一个static 方法,名为 randShape()。它的作用是在每次调用它时为某个随机选 

择的Shape 对象生成一个句柄。请注意上溯造型是在每个 return 语句里发生的。这个语句取得指向一个 

Circle,Square 或者Triangle 的句柄,并将其作为返回类型 Shape 发给方法。所以无论什么时候调用这个 

方法,就绝对没机会了解它的具体类型到底是什么,因为肯定会获得一个单纯的 Shape 句柄。  

main()包含了 Shape 句柄的一个数组,其中的数据通过对randShape()的调用填入。在这个时候,我们知道 

自己拥有Shape,但不知除此之外任何具体的情况(编译器同样不知)。然而,当我们在这个数组里步进, 

并为每个元素调用 draw()的时候,与各类型有关的正确行为会魔术般地发生,就象下面这个输出示例展示的 

那样:  

  

Circle。draw()  

Triangle。draw()  

Circle。draw()  

Circle。draw()  

Circle。draw()  

Square。draw()  

Triangle。draw()  

Square。draw()  

Square。draw()  

  

当然,由于几何形状是每次随机选择的,所以每次运行都可能有不同的结果。之所以要突出形状的随机选 

择,是为了让大家深刻体会这一点:为了在编译的时候发出正确的调用,编译器毋需获得任何特殊的情报。 

对 draw()的所有调用都是通过动态绑定进行的。  



7。2。3  扩展性  



现在,让我们仍然返回乐器(Instrument)示例。由于存在多形性,所以可根据自己的需要向系统里加入任 

意多的新类型,同时毋需更改 true()方法。在一个设计良好的 OOP 程序中,我们的大多数或者所有方法都会 

遵从 tune()的模型,而且只与基础类接口通信。我们说这样的程序具有“扩展性”,因为可以从通用的基础 

类继承新的数据类型,从而新添一些功能。如果是为了适应新类的要求,那么对基础类接口进行操纵的方法 

根本不需要改变,  

对于乐器例子,假设我们在基础类里加入更多的方法,以及一系列新类,那么会出现什么情况呢?下面是示 

意图:  

  



                                                                                  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 的意图是为从它衍生出去的所有类都创建一个通用接口。  
返回目录 上一页 下一页 回到顶部 0 0
未阅读完?加入书签已便下次继续阅读!
温馨提示: 温看小说的同时发表评论,说出自己的看法和其它小伙伴们分享也不错哦!发表书评还可以获得积分和经验奖励,认真写原创书评 被采纳为精评可以获得大量金币、积分和经验奖励哦!