友情提示:如果本网页打开太慢或显示不完整,请尝试鼠标右键“刷新”本网页!
富士康小说网 返回本书目录 加入书签 我的书架 我的书签 TXT全本下载 『收藏到我的浏览器』

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

快捷操作: 按键盘上方向键 ← 或 → 可快速上下翻页 按键盘上的 Enter 键可回到本书目录页 按键盘上方向键 ↑ 可回到本页顶部! 如果本书没有阅读完,想下次继续接着阅读,可使用上方 "收藏到我的浏览器" 功能 和 "加入书签" 功能!



个例子。多形性是一种不可独立应用的特性(就象一个 switch 语句),只可与其他元素协同使用。我们应将 

其作为类总体关系的一部分来看待。人们经常混淆 Java 其他的、非面向对象的特性,比如方法过载等,这些 

特性有时也具有面向对象的某些特征。但不要被愚弄:如果以后没有绑定,就不成其为多形性。  

为使用多形性乃至面向对象的技术,特别是在自己的程序中,必须将自己的编程视野扩展到不仅包括单独一 

个类的成员和消息,也要包括类与类之间的一致性以及它们的关系。尽管这要求学习时付出更多的精力,但 

却是非常值得的,因为只有这样才可真正有效地加快自己的编程速度、更好地组织代码、更容易做出包容面 

广的程序以及更易对自己的代码进行维护与扩展。  



7。10 练习  



(1) 创建Rodent (啮齿动物):Mouse (老鼠);Gerbil (鼹鼠);Hamster (大颊鼠)等的一个继承分级结 

构。在基础类中,提供适用于所有 Rodent 的方法,并在衍生类中覆盖它们,从而根据不同类型的Rodent 采 

取不同的行动。创建一个Rodent 数组,在其中填充不同类型的 Rodent,然后调用自己的基础类方法,看看 

会有什么情况发生。  

(2) 修改练习 1,使Rodent 成为一个接口。  

(3) 改正WindError。java 中的问题。  

(4) 在GreenhouseControls。java 中,添加Event 内部类,使其能打开和关闭风扇。  



                                                                       208 


…………………………………………………………Page 210……………………………………………………………

                       第 8 章 对象的容纳  



  

 “如果一个程序只含有数量固定的对象,而且已知它们的存在时间,那么这个程序可以说是相当简单的。”  

  

通常,我们的程序需要根据程序运行时才知道的一些标准创建新对象。若非程序正式运行,否则我们根本不 

知道自己到底需要多少数量的对象,甚至不知道它们的准确类型。为了满足常规编程的需要,我们要求能在 

任何时候、任何地点创建任意数量的对象。所以不可依赖一个已命名的句柄来容纳自己的每一个对象,就象 

下面这样:  

MyObject myHandle;  

因为根本不知道自己实际需要多少这样的东西。  

为解决这个非常关键的问题,Java 提供了容纳对象(或者对象的句柄)的多种方式。其中内建的类型是数 

组,我们之前已讨论过它,本章准备加深大家对它的认识。此外,Java 的工具(实用程序)库提供了一些 

 “集合类”(亦称作“容器类”,但该术语已由AWT 使用,所以这里仍采用“集合”这一称呼)。利用这些 

集合类,我们可以容纳乃至操纵自己的对象。本章的剩余部分会就此进行详细讨论。  



8。1 数组  



对数组的大多数必要的介绍已在第 4 章的最后一节进行。通过那里的学习,大家已知道自己该如何定义及初 

始化一个数组。对象的容纳是本章的重点,而数组只是容纳对象的一种方式。但由于还有其他大量方法可容 

纳数组,所以是哪些地方使数组显得如此特别呢?  

有两方面的问题将数组与其他集合类型区分开来:效率和类型。对于Java 来说,为保存和访问一系列对象 

 (实际是对象的句柄)数组,最有效的方法莫过于数组。数组实际代表一个简单的线性序列,它使得元素的 

访问速度非常快,但我们却要为这种速度付出代价:创建一个数组对象时,它的大小是固定的,而且不可在 

那个数组对象的“存在时间”内发生改变。可创建特定大小的一个数组,然后假如用光了存储空间,就再创 

建一个新数组,将所有句柄从旧数组移到新数组。这属于“矢量”(Vector)类的行为,本章稍后还会详细 

讨论它。然而,由于为这种大小的灵活性要付出较大的代价,所以我们认为矢量的效率并没有数组高。  

C++的矢量类知道自己容纳的是什么类型的对象,但同 Java 的数组相比,它却有一个明显的缺点:C++矢量类 

的operator''不能进行范围检查,所以很容易超出边界(然而,它可以查询 vector 有多大,而且at()方法 

确实能进行范围检查)。在Java 中,无论使用的是数组还是集合,都会进行范围检查——若超过边界,就会 

获得一个RuntimeException (运行期违例)错误。正如大家在第9 章会学到的那样,这类违例指出的是一个 

程序员错误,所以不需要在代码中检查它。在另一方面,由于 C++的vector 不进行范围检查,所以访问速度 

较快——在 Java 中,由于对数组和集合都要进行范围检查,所以对性能有一定的影响。  

本章还要学习另外几种常见的集合类:Vector (矢量)、Stack (堆栈)以及Hashtable (散列表)。这些类 

都涉及对对象的处理——好象它们没有特定的类型。换言之,它们将其当作 Object 类型处理(Object 类型 

是Java 中所有类的“根”类)。从某个角度看,这种处理方法是非常合理的:我们仅需构建一个集合,然后 

任何Java 对象都可以进入那个集合(除基本数据类型外——可用Java 的基本类型封装类将其作为常数置入 

集合,或者将其封装到自己的类内,作为可以变化的值使用)。这再一次反映了数组优于常规集合:创建一 

个数组时,可令其容纳一种特定的类型。这意味着可进行编译期类型检查,预防自己设置了错误的类型,或 

者错误指定了准备提取的类型。当然,在编译期或者运行期,Java 会防止我们将不当的消息发给一个对象。 

所以我们不必考虑自己的哪种做法更加危险,只要编译器能及时地指出错误,同时在运行期间加快速度,目 

的也就达到了。此外,用户很少会对一次违例事件感到非常惊讶的。  

考虑到执行效率和类型检查,应尽可能地采用数组。然而,当我们试图解决一个更常规的问题时,数组的局 

限也可能显得非常明显。在研究过数组以后,本章剩余的部分将把重点放到Java 提供的集合类身上。  



8。1。1  数组和第一类对象  



无论使用的数组属于什么类型,数组标识符实际都是指向真实对象的一个句柄。那些对象本身是在内存 

 “堆”里创建的。堆对象既可“隐式”创建(即默认产生),亦可“显式”创建(即明确指定,用一个new 

表达式)。堆对象的一部分(实际是我们能访问的唯一字段或方法)是只读的 length (长度)成员,它告诉 

我们那个数组对象里最多能容纳多少元素。对于数组对象,“''”语法是我们能采用的唯一另类访问方法。  

下面这个例子展示了对数组进行初始化的不同方式,以及如何将数组句柄分配给不同的数组对象。它也揭示 



                                                             209 


…………………………………………………………Page 211……………………………………………………………

出对象数组和基本数据类型数组在使用方法上几乎是完全一致的。唯一的差别在于对象数组容纳的是句柄, 

而基本数据类型数组容纳的是具体的数值(若在执行此程序时遇到困难,请参考第3 章的“赋值”小节):  

  

//: ArraySize。java  

// Initialization & re…assignment of arrays  

package c08;  

  

class Weeble {} // A small mythical creature  

  

public class ArraySize {  

  public static void main(String'' args) {  

    // Arrays of objects:  

    Weeble'' a; // Null handle  

    Weeble'' b = new Weeble'5'; // Null handles  

    Weeble'' c = new Weeble'4';  

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

      c'i' = new Weeble();  

    Weeble'' d = {  

      new Weeble(); new Weeble(); new Weeble()  

    };  

    // pile error: variable a not initialized:  

    //!System。out。println(〃a。length=〃 + a。length);  

    System。out。println(〃b。length = 〃 + b。length);  

    // The handles inside the array are   

    // automatically initialized to null:  

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

      System。out。println(〃b'〃 + i + 〃'=〃 + b'i');  

    System。out。println(〃c。length = 〃 + c。length);  

    System。out。println(〃d。length = 〃 + d。length);  

    a = d;  

    System。out。println(〃a。length = 〃 + a。length);  

    // Java 1。1 initialization syntax:  

    a = new Weeble'' {  

      new Weeble(); new Weeble()  

    };  

    System。out。println(〃a。length = 〃 + a。length);  

  

    // Arrays of primitives:  

    int'' e; // Null handle  

    int'' f = new int'5';  

    int'' g = new int'4';  

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

      g'i' = i*i;  

    int'' h = { 11; 47; 93 };  

    // pile error: variable e not initialized:  

    //!System。out。println(〃e。length=〃 + e。length);  

    System。out。println(〃f。length = 〃 + f。length);  

    // The primitives inside the array are  

    // automatically initialized to zero:  

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

      System。out。println(〃f'〃 + i + 〃'=〃 + f'i');  

    System。out。println(〃g。length = 〃 + g。length);  



                                                                                          210 


…………………………………………………………Page 212……………………………………………………………

    System。out。println(〃h。length = 〃 + h。length);  

    e = h;  

    System。out。println(〃e。length = 〃 + e。length);  

    // Java 1。1 initialization syntax:  

    e = new int'' { 1; 2 };  

    System。out。println(〃e。length = 〃 + e。length);  

  }  

} ///:~  

Here’s the output from the program:  

  

  

b。length = 5  

b'0'=null  

b'1'=null  

b'2'=null  

b'3'=null  

b'4'=null  

c。length = 4  

d。length = 3  

a。length = 3  

a。length = 2  

f。length = 5  

f'0'=0  

f'1'=0  

f'2'=0  

f'3'=0  

f'4'=0  

g。length = 4  

h。length = 3  

e。length = 3  

e。length = 2  

  

其中,数组 a 只是初始化成一个 null 句柄。此时,编译器会禁止我们对这个句柄作任何实际操作,除非已正 

确地初始化了它。数组 b 被初始化成指向由 Weeble 句柄构成的一个数组,但那个数组里实际并未放置任何 

Weeble 对象。然而,我们仍然可以查询那个数组的大小,因为 b 指向的是一个合法对象。这也为我们带来了 

一个难题:不可知道那个数组里实际包含了多少个元素,因为 length 只告诉我们可将多少元素置入那个数 

组。换言之,我们只知道数组对象的大小或容量,不知其实际容纳了多少个元素。尽管如此,由于数组对象 

在创建之初会自动初始化成null,所以可检查它是否为 null,判断一个特定的数组“空位”是否容纳一个对 

象。类似地,由基本数据类型构成的数组会自动初始化成零(针对数值类型)、null (字符类型)或者 

false (布尔类型)。  

数组c 显示出我们首先创建一个数组对象,再将Weeble 对象赋给那个数组的所有“空位”。数组 d 揭示出 

 “集合初始化”语法,从而创建数组对象(用new 命令明确进行,类似于数组c),然后用Weeble 对象进行 

初始化,全部工作在一条语句里完成。  

下面这个表达式:  

  

a = d;  

  

向我们展示了如何取得同一个数组对象连接的句柄,然后将其赋给另一个数组对象,就象我们针对对象句柄 

的其他任何类型做的那样。现在,a 和 d 都指向内存堆内同样的数组对象。  

Java 1。1 加入了一种新的数组初始化语法,可将其想象成“动态集合初始化”。由 d 采用的 Java 1。0 集合 

初始化方法则必须在定义d 的同时进行。但若采用 Java 1。1 的语法,却可以在任何地方创建和初始化一个数 

组对象。例如,假设hide()方法用于取得一个Weeble 对象数组,那么调用它时传统的方法是:  



                                                                                          211 


…………………………………………………………Page 213……………………………………………………………

hide(d);  

但在Java 1。1 中,亦可动态创建想作为参数传递的数组,如下所示:  

hide(new Weeble'' {new Weeble(); new Weeble() });  

这一新式语法使我们在某些场合下写代码更方便了。  

上述例子的第二部分揭示出这样一个问题:对于由基本数据类型构成的数组,它们的运作方式与对象数组极 

为相似,只是前者直接包容了基本类型的数据值。  

  

1。 基本数据类型集合  

集合类只能容纳对象句柄。但对一个数组,却既可令其直接容纳基本类型的数据,亦可容纳指向对象的句 

柄。利用象 Integer、Double 之类的“封装器”类,可将基本数据类型的值置入一个集合里。但正如本章后 

面会在WordCount。java 例子中讲到的那样,用于基本数据类型的封装器类只是在某些场合下才能发挥作用。 

无论将基本类型的数据置入数组,还是将其封装进入位于集合的一个类内,都涉及到执行效率的问题。显 

然,若能创建和访问一个基本数据类型数组,那么比起访问一个封装数据的集合,前者的效率会高出许多。  

当然,假如准备一种基本数据类型,同时又想要集合的灵活性(在需要的时候可自动扩展,腾出更多的空 

间),就不宜使用数组,必须使用由封装的数据构成的一个集合。大家或许认为针对每种基本数据类型,都 

应有一种特殊类型的Vector。但Java 并未提供这一特性。某些形式的建模机制或许会在某一天帮助 Java 更 

好地解决这个问题(注释①)。  

  

①:这儿是 C++比Java 做得好的一个地方,因为C++通过 template 关键字提供了对“参数化类型”的支持。  



8。1。2  数组的返回  



假定我们现在想写一个方法,同时不希望它仅仅返回一样东西,而是想返回一系列东西。此时,象C 和C++ 

这样的语言会使问题复杂化,因为我们不能返回一个数组,只能返回指向数组的一个指针。这样就非常麻 

烦,因为很难控制数组的“存在时间”,它很容易造成内存“漏洞”的出现。  

Java 采用的是类似的方法,但我们能“返回一个数组”。当然,此时返回的实际仍是指向数组的指针。但在 

Java 里,我们永远不必担心那个数组的是否可用——只要需要,它就会自动存在。而且垃圾收集器会在我们 

完成后自动将其清除。  

作为一个例子,请思考如何返回一个字串数组:  

  

//: IceCream。java  

// Returning arrays from methods  

  

public class IceCream {  

  static String'' flav = {  

    〃Chocolate〃; 〃Strawberry〃;  

    〃Vanilla Fudge Swirl〃; 〃Mint Chip〃;  

    〃Mocha Almond Fudge〃; 〃Rum Raisin〃;  

    〃Praline Cream〃; 〃Mud Pie〃   

  };  

  static String'' flavorSet(int n) {  

    // Force it to be positive & within bounds:  

    n = Math。abs(n) % (flav。length + 1);  

    String'' results = new String'n';  

    int'' picks = new int'n';  

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

      picks'i' = …1;  

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

      retry:  

      while(true) {  

        int t =  

           (int)(Math。random() * flav。length);  

        for(int j = 0; j 《 i; j++)  



                                                                                    212 


…………………………………………………………Page 214……………………………………………………………

          if(picks'j' == t) continue retry;  

        picks'i' = t;  

        results'i' = flav't';  

        break;  

      }  

    }  

    return results;  

  }  

  public static void main(String'' args) {  

    for(int i = 0; i 《 20; i++) {  

      System。out。println(  

        〃flavorSet(〃 + i + 〃) = 〃);  

      String'' fl = flavorSet(flav。length);  

      for(int j = 0; j 《 fl。length; j++)  

        System。out。println(〃t〃 + fl'j');  

    }  

  }  

} ///:~  

  

flavorSet()方法创建了一个名为 results 的String 数组。该数组的大小为 n——具体数值取决于我们传递 

给方法的自变量。随后,它从数组 flav 里随机挑选一些“香料”(Flavor),并将它们置入results 里,并 

最终返回results。返回数组与返回其他任何对象没什么区别——最终返回的都是一个句柄。至于数组到底 

是在flavorSet()里创建的,还是在其他什么地方创建的,这个问题并不重要,因为反正返回的仅是一个句 

柄。一旦我们的操作完成,垃圾收集器会自动关照数组的清除工作。而且只要我们需要数组,它就会乖乖地 

听候调遣。  

另一方面,注意当 flavorSet()随机挑选香料的时候,它需要保证以前出现过的一次随机选择不会再次出 

现。为达到这个目的,它使用了一个无限while 循环,不断地作出随机选择,直到发现未在picks 数组里出 

现过的一个元素为止(当然,也可以进行字串比较,检查随机选择是否在 results 数组里出现过,但字串比 

较的效率比较低)。若成功,就添加这个元素,并中断循环(break),再查找下一个(i 值会递增)。但假 

若 t 是一个已在 picks 里出现过的数组,就用标签式的continue 往回跳两级,强制选择一个新 t。用一个调 

试程序可以很清楚地看到这个过程。  

main()能显示出 20 个完整的香料集合,所以我们看到 flavorSet()每次都用一个随机顺序选择香料。为体会 

这一点,最简单的方法就是将输出重导向进入一个文件,然后直接观看这个文件的内容。  



8。2 集合  



现在总结一下我们前面学过的东西:为容纳一组对象,最适宜的选择应当是数组。而且假如容纳的是一系列 

基本数据类型,更是必须采用数组。在本章剩下的部分,大家将接触到一些更常规的情况。当我们编写程序 

时,通常并不能确切地知道最终需要多少个对象。有些时候甚至想用更复杂的方式来保存对象。为解决这个 

问题,Java 提供了四种类型的“集合类”:Vector (矢量)、BitSet (位集)、Stack (堆栈)以及 

Hashtable (散列表)。与拥有集合功能的其他语言相比,尽管这儿的数量显得相当少,但仍然能用它们解决 

数量惊人的实际问题。  

这些集合类具有形形色色的特征。例如,Stack 实现了一个 LIFO (先入先出)序列,而Hashtable 是一种 

 “关联数组”,允许我们将任何对象关联起来。除此以外,所有Java 集合类都能自动改变自身的大小。所 

以,我们在编程时可使用数量众多的对象,同时不必担心会将集合弄得有多大。  



8。2。1  缺点:类型未知  



使用Java 集合的“缺点”是在将对象置入一个集合时丢失了类型信息。之�
返回目录 上一页 下一页 回到顶部 赞(11) 踩(15)
快捷操作: 按键盘上方向键 ← 或 → 可快速上下翻页 按键盘上的 Enter 键可回到本书目录页 按键盘上方向键 ↑ 可回到本页顶部!
温馨提示: 温看小说的同时发表评论,说出自己的看法和其它小伙伴们分享也不错哦!发表书评还可以获得积分和经验奖励,认真写原创书评 被采纳为精评可以获得大量金币、积分和经验奖励哦!