八宝书库 > 文学其他电子书 > Java编程思想第4版[中文版](PDF格式) >

第49部分

Java编程思想第4版[中文版](PDF格式)-第49部分

小说: Java编程思想第4版[中文版](PDF格式) 字数: 每页4000字

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




二净。因此,程序员不得不猜测到底应该在哪里进行优化。在标准库里居然采用了如此笨拙的设计,真不敢 

想象会在程序员里引发什么样的情绪。  

另一个值得注意的是Hashtable (散列表),它是另一个重要的标准类。该类没有采用任何final 方法。正 

如我们在本书其他地方提到的那样,显然一些类的设计人员与其他设计人员有着全然不同的素质(注意比较 

Hashtable 极短的方法名与Vecor 的方法名)。对类库的用户来说,这显然是不应该如此轻易就能看出的。 

一个产品的设计变得不一致后,会加大用户的工作量。这也从另一个侧面强调了代码设计与检查时需要很强 

的责任心。  



6。9 初始化和类装载  



在许多传统语言里,程序都是作为启动过程的一部分一次性载入的。随后进行的是初始化,再是正式执行程 

序。在这些语言中,必须对初始化过程进行慎重的控制,保证 static数据的初始化不会带来麻烦。比如在一 

个 static 数据获得初始化之前,就有另一个 static数据希望它是一个有效值,那么在 C++中就会造成问 

题。  

Java 则没有这样的问题,因为它采用了不同的装载方法。由于 Java 中的一切东西都是对象,所以许多活动 

变得更加简单,这个问题便是其中的一例。正如下一章会讲到的那样,每个对象的代码都存在于独立的文件 

中。除非真的需要代码,否则那个文件是不会载入的。通常,我们可认为除非那个类的一个对象构造完毕, 

否则代码不会真的载入。由于 static 方法存在一些细微的歧义,所以也能认为“类代码在首次使用的时候载 

入”。  

首次使用的地方也是static 初始化发生的地方。装载的时候,所有 static对象和 static代码块都会按照本 

来的顺序初始化(亦即它们在类定义代码里写入的顺序)。当然,static 数据只会初始化一次。  



6。9。1  继承初始化  



我们有必要对整个初始化过程有所认识,其中包括继承,对这个过程中发生的事情有一个整体性的概念。请 

观察下述代码:  

  

//: Beetle。java  

// The full process of initialization。  

  

class Insect {  

  int i = 9;  

  int j;  

  Insect() {  

    prt(〃i = 〃 + i + 〃; j = 〃 + j);  

    j = 39;  

  }  

  static int x1 =   

    prt(〃static Insect。x1 initialized〃);  

  static int prt(String s) {  

    System。out。println(s);  

    return 47;  

  }  

}  

  



                                                                              157 


…………………………………………………………Page 159……………………………………………………………

public class Beetle extends Insect {  

  int k = prt(〃Beetle。k initialized〃);  

  Beetle() {  

    prt(〃k = 〃 + k);  

    prt(〃j = 〃 + j);  

  }  

  static int x2 =  

    prt(〃static Beetle。x2 initialized〃);  

  static int prt(String s) {  

    System。out。println(s);  

    return 63;  

  }  

  public static void main(String'' args) {  

    prt(〃Beetle constructor〃);  

    Beetle b = new Beetle();  

  }  

} ///:~  

  

该程序的输出如下:  

  

static Insect。x initialized  

static Beetle。x initialized  

Beetle constructor  

i = 9; j = 0  

Beetle。k initialized  

k = 63  

j = 39  

  

对Beetle 运行Java 时,发生的第一件事情是装载程序到外面找到那个类。在装载过程中,装载程序注意它 

有一个基础类(即 extends 关键字要表达的意思),所以随之将其载入。无论是否准备生成那个基础类的一 

个对象,这个过程都会发生(请试着将对象的创建代码当作注释标注出来,自己去证实)。  

若基础类含有另一个基础类,则另一个基础类随即也会载入,以此类推。接下来,会在根基础类(此时是 

Insect)执行 static 初始化,再在下一个衍生类执行,以此类推。保证这个顺序是非常关键的,因为衍生类 

的初始化可能要依赖于对基础类成员的正确初始化。  

此时,必要的类已全部装载完毕,所以能够创建对象。首先,这个对象中的所有基本数据类型都会设成它们 

的默认值,而将对象句柄设为null 。随后会调用基础类构建器。在这种情况下,调用是自动进行的。但也完 

全可以用 super 来自行指定构建器调用(就象在Beetle()构建器中的第一个操作一样)。基础类的构建采用 

与衍生类构建器完全相同的处理过程。基础顺构建器完成以后,实例变量会按本来的顺序得以初始化。最 

后,执行构建器剩余的主体部分。  



6。10 总结  



无论继承还是合成,我们都可以在现有类型的基础上创建一个新类型。但在典型情况下,我们通过合成来实 

现现有类型的“再生”或“重复使用”,将其作为新类型基础实施过程的一部分使用。但如果想实现接口的 

 “再生”,就应使用继承。由于衍生或派生出来的类拥有基础类的接口,所以能够将其“上溯造型”为基础 

类。对于下一章要讲述的多形性问题,这一点是至关重要的。  

尽管继承在面向对象的程序设计中得到了特别的强调,但在实际启动一个设计时,最好还是先考虑采用合成 

技术。只有在特别必要的时候,才应考虑采用继承技术(下一章还会讲到这个问题)。合成显得更加灵活。 

但是,通过对自己的成员类型应用一些继承技巧,可在运行期准确改变那些成员对象的类型,由此可改变它 

们的行为。  

尽管对于快速项目开发来说,通过合成和继承实现的代码再生具有很大的帮助作用。但在允许其他程序员完 

全依赖它之前,一般都希望能重新设计自己的类结构。我们理想的类结构应该是每个类都有 自己特定的用 



                                                                                   158 


…………………………………………………………Page 160……………………………………………………………

途。它们不能过大(如集成的功能太多,则很难实现它的再生),也不能过小(造成不能由自己使用,或者 

不能增添新功能)。最终实现的类应该能够方便地再生。  



6。11 练习  



(1) 用默认构建器(空自变量列表)创建两个类:A 和 B,令它们自己声明自己。从A 继承一个名为 C 的新 

类,并在C 内创建一个成员B。不要为C 创建一个构建器。创建类C 的一个对象,并观察结果。  

(2) 修改练习 1,使A 和B 都有含有自变量的构建器,则不是采用默认构建器。为C 写一个构建器,并在C 

的构建器中执行所有初始化工作。  

(3) 使用文件Cartoon。java ,将Cartoon 类的构建器代码变成注释内容标注出去。解释会发生什么事情。  

(4) 使用文件Chess。java,将Chess 类的构建器代码作为注释标注出去。同样解释会发生什么。  



                                                                  159 


…………………………………………………………Page 161……………………………………………………………

                                  第 7 章  多形性  



  

 “对于面向对象的程序设计语言,多型性是第三种最基本的特征(前两种是数据抽象和继承。”  

  

 “多形性”(Polymorphism)从另一个角度将接口从具体的实施细节中分离出来,亦即实现了“是什么”与 

 “怎样做”两个模块的分离。利用多形性的概念,代码的组织以及可读性均能获得改善。此外,还能创建 

 “易于扩展”的程序。无论在项目的创建过程中,还是在需要加入新特性的时候,它们都可以方便地“成 

长”。  

通过合并各种特征与行为,封装技术可创建出新的数据类型。通过对具体实施细节的隐藏,可将接口与实施 

细节分离,使所有细节成为“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()〃);  

返回目录 上一页 下一页 回到顶部 0 1

你可能喜欢的