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

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

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



…………………………………………………………Page 217……………………………………………………………

    for(int i = 0; i 《 mice。size(); i++) {  

      // No cast necessary; automatic call  

      // to Object。toString():  

      System。out。println(  

        〃Free mouse: 〃 + mice。elementAt(i));  

      MouseTrap。caughtYa(mice。elementAt(i));  

    }  

  }  

} ///:~  

  

可在Mouse 里看到对toString()的重定义代码。在main()的第二个for 循环中,可发现下述语句:  

  

System。out。println(〃Free mouse: 〃 +  

mice。elementAt(i));  

  

在“+”后,编译器预期看到的是一个String 对象。elementAt()生成了一个 Object,所以为获得希望的 

String,编译器会默认调用toString()。但不幸的是,只有针对String 才能得到象这样的结果;其他任何 

类型都不会进行这样的转换。  

隐藏造型的第二种方法已在Mousetrap 里得到了应用。caughtYa()方法接收的不是一个Mouse,而是一个 

Object。随后再将其造型为一个Mouse。当然,这样做是非常冒失的,因为通过接收一个 Object,任何东西 

都可以传递给方法。然而,假若造型不正确——如果我们传递了错误的类型——就会在运行期间得到一个违 

例错误。这当然没有在编译期进行检查好,但仍然能防止问题的发生。注意在使用这个方法时毋需进行造 

型:  

MouseTrap。caughtYa(mice。elementAt(i));  

  

2。 生成能自动判别类型的 Vector  

大家或许不想放弃刚才那个问题。一个更“健壮”的方案是用 Vector 创建一个新类,使其只接收我们指定的 

类型,也只生成我们希望的类型。如下所示:  

  

//: GopherVector。java  

// A type…conscious Vector  

import java。util。*;  

  

class Gopher {  

  private int gopherNumber;  

  Gopher(int i) {  

    gopherNumber = i;  

  }  

  void print(String msg) {  

    if(msg != null) System。out。println(msg);  

    System。out。println(  

      〃Gopher number 〃 + gopherNumber);  

  }  

}  

  

class GopherTrap {  

  static void caughtYa(Gopher g) {  

    g。print(〃Caught one!〃);  

  }  

}  

  

class GopherVector {  



                                                                                             216 


…………………………………………………………Page 218……………………………………………………………

  private Vector v = new Vector ();  

  public void addElement(Gopher m) {  

    v。addElement(m);  

  }  

  public Gopher elementAt(int index) {  

    return (Gopher)v。elementAt(index);  

  }  

  public int size() { return v。size(); }  

  public static void main(String'' args) {  

    GopherVector gophers = new GopherVector();  

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

      gophers。addElement(new Gopher(i));  

    for(int i = 0; i 《 gophers。size(); i++)  

      GopherTrap。caughtYa(gophers。elementAt(i));  

  }  

} ///:~  

  

这前一个例子类似,只是新的 GopherVector 类有一个类型为Vector 的private 成员(从Vector 继承有些麻 

烦,理由稍后便知),而且方法也和Vector 类似。然而,它不会接收和产生普通Object,只对 Gopher 对象 

感兴趣。  

由于GopherVector 只接收一个 Gopher (地鼠),所以假如我们使用:  

gophers。addElement(new Pigeon());  

就会在编译期间获得一条出错消息。采用这种方式,尽管从编码的角度看显得更令人沉闷,但可以立即判断 

出是否使用了正确的类型。  

注意在使用 elementAt()时不必进行造型——它肯定是一个Gopher。  

  

3。 参数化类型  

这类问题并不是孤立的——我们许多时候都要在其他类型的基础上创建新类型。此时,在编译期间拥有特定 

的类型信息是非常有帮助的。这便是“参数化类型”的概念。在C++中,它由语言通过“模板”获得了直接 

支持。至少,Java 保留了关键字 generic,期望有一天能够支持参数化类型。但我们现在无法确定这一天何 

时会来临。  



8。3 枚举器(反复器)  



在任何集合类中,必须通过某种方法在其中置入对象,再用另一种方法从中取得对象。毕竟,容纳各种各样 

的对象正是集合的首要任务。在Vector 中,addElement()便是我们插入对象采用的方法,而 elementAt()是 

提取对象的唯一方法。Vector 非常灵活,我们可在任何时候选择任何东西,并可使用不同的索引选择多个元 

素。  

若从更高的角度看这个问题,就会发现它的一个缺陷:需要事先知道集合的准确类型,否则无法使用。乍看 

来,这一点似乎没什么关系。但假若最开始决定使用Vector,后来在程序中又决定(考虑执行效率的原因) 

改变成一个 List (属于Java1。2 集合库的一部分),这时又该如何做呢?  

可利用“反复器”(Iterator)的概念达到这个目的。它可以是一个对象,作用是遍历一系列对象,并选择 

那个序列中的每个对象,同时不让客户程序员知道或关注那个序列的基础结构。此外,我们通常认为反复器 

是一种“轻量级”对象;也就是说,创建它只需付出极少的代价。但也正是由于这个原因,我们常发现反复 

器存在一些似乎很奇怪的限制。例如,有些反复器只能朝一个方向移动。  

Java 的Enumeration (枚举,注释②)便是具有这些限制的一个反复器的例子。除下面这些外,不可再用它 

做其他任何事情:  

(1) 用一个名为 elements()的方法要求集合为我们提供一个 Enumeration。我们首次调用它的 nextElement() 

时,这个Enumeration 会返回序列中的第一个元素。  

(2) 用nextElement() 获得下一个对象。  

(3) 用hasMoreElements()检查序列中是否还有更多的对象。  

  

②:“反复器”这个词在C++和OOP 的其他地方是经常出现的,所以很难确定为什么Java 的开发者采用了这 



                                                                                217 


…………………………………………………………Page 219……………………………………………………………

样一个奇怪的名字。Java 1。2 的集合库修正了这个问题以及其他许多问题。  

  

只可用Enumeration 做这些事情,不能再有更多。它属于反复器一种简单的实现方式,但功能依然十分强 

大。为体会它的运作过程,让我们复习一下本章早些时候提到的CatsAndDogs。java 程序。在原始版本中, 

elementAt()方法用于选择每一个元素,但在下述修订版中,可看到使用了一个“枚举”:  

  

//: CatsAndDogs2。java  

// Simple collection with Enumeration  

import java。util。*;  

  

class Cat2 {  

  private int catNumber;  

  Cat2(int i) {  

    catNumber = i;  

  }  

  void print() {  

    System。out。println(〃Cat number 〃 +catNumber);  

  }  

}  

  

class Dog2 {  

  private int dogNumber;  

  Dog2(int i) {  

    dogNumber = i;  

  }  

  void print() {  

    System。out。println(〃Dog number 〃 +dogNumber);  

  }  

}  

  

public class CatsAndDogs2 {  

  public static void main(String'' args) {  

    Vector cats = new Vector();  

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

      cats。addElement(new Cat2(i));  

    // Not a problem to add a dog to cats:  

    cats。addElement(new Dog2(7));  

    Enumeration e = cats。elements();  

    while(e。hasMoreElements())  

      ((Cat2)e。nextElement())。print();  

    // Dog is detected only at run…time  

  }  

} ///:~  

  

我们看到唯一的改变就是最后几行。不再是:  

  

for(int i = 0; i 《 cats。size(); i++)  

((Cat)cats。elementAt(i))。print();  

  

而是用一个 Enumeration 遍历整个序列:  

  

while(e。hasMoreElements())  



                                                                                             218 


…………………………………………………………Page 220……………………………………………………………

((Cat2)e。nextElement())。print();  

  

使用Enumeration,我们不必关心集合中的元素数量。所有工作均由 hasMoreElements()和nextElement()自 

动照管了。  

下面再看看另一个例子,让我们创建一个常规用途的打印方法:  

  

//: HamsterMaze。java  

// Using an Enumeration  

import java。util。*;  

  

class Hamster {  

  private int hamsterNumber;  

  Hamster(int i) {  

    hamsterNumber = i;  

  }  

  public String toString() {  

    return 〃This is Hamster #〃 + hamsterNumber;  

  }  

}  

  

class Printer {  

  static void printAll(Enumeration e) {  

    while(e。hasMoreElements())  

      System。out。println(  

        e。nextElement()。toString());  

  }  

}  

  

public class HamsterMaze {  

  public static void main(String'' args) {  

    Vector v = new Vector();  

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

      v。addElement(new Hamster(i));  

    Printer。printAll(v。elements());  

  }  

} ///:~  

  

仔细研究一下打印方法:  

  

static void printAll(Enumeration e) {  

  while(e。hasMoreElements())  

    System。out。println(  

      e。nextElement()。toString());  

}  

  

注意其中没有与序列类型有关的信息。我们拥有的全部东西便是Enumeration。为了解有关序列的情况,一 

个Enumeration 便足够了:可取得下一个对象,亦可知道是否已抵达了末尾。取得一系列对象,然后在其中 

遍历,从而执行一个特定的操作——这是一个颇有价值的编程概念,本书许多地方都会沿用这一思路。  

这个看似特殊的例子甚至可以更为通用,因为它使用了常规的 toString()方法(之所以称为常规,是由于它 

属于Object 类的一部分)。下面是调用打印的另一个方法(尽管在效率上可能会差一些):  

System。out。println(〃〃 + e。nextElement());  

它采用了封装到Java 内部的“自动转换成字串”技术。一旦编译器碰到一个字串,后面跟随一个“+”,就 



                                                                                             219 


…………………………………………………………Page 221……………………………………………………………

会希望后面又跟随一个字串,并自动调用 toString()。在Java 1。1 中,第一个字串是不必要的;所有对象 

都会转换成字串。亦可对此执行一次造型,获得与调用toString()同样的效果:  

System。out。println((String)e。nextElement())  

但我们想做的事情通常并不仅仅是调用Object 方法,所以会再度面临类型造型的问题。对于自己感兴趣的类 

型,必须假定自己已获得了一个Enumeration,然后将结果对象造型成为那种类型(若操作错误,会得到运 

行期违例)。  



8。4 集合的类型  



标准Java 1。0 和 1。1库配套提供了非常少的一系列集合类。但对于自己的大多数编程要求,它们基本上都能 

胜任。正如大家到本章末尾会看到的,Java 1。2 提供的是一套重新设计过的大型集合库。  



8。4。1 Vector  



Vector 的用法很简单,这已在前面的例子中得到了证明。尽管我们大多数时候只需用addElement()插入对 

象,用 elementAt()一次提取一个对象,并用elements()获得对序列的一个“枚举”。但仍有其他一系列方 

法是非常有用的。同我们对于 Java 库惯常的做法一样,在这里并不使用或讲述所有这些方法。但请务必阅读 

相应的电子文档,对它们的工作有一个大概的认识。  

  

1。 崩溃Java  

Java 标准集合里包含了 toString()方法,所以它们能生成自己的 String 表达方式,包括它们容纳的对象。 

例如在Vector 中,toString()会在Vector 的各个元素中步进和遍历,并为每个元素调用 toString()。假定 

我们现在想打印出自己类的地址。看起来似乎简单地引用 this 即可(特别是C++程序员有这样做的倾向):  

  

//: CrashJava。java  

// One way to crash Java  

import java。util。*;  

  

public class CrashJava {  

  public String toString() {  

    return 〃CrashJava address: 〃 + this + 〃n〃;  

  }  

  public static void main(String'' args) {  

    Vector v = new Vector();  

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

      v。addElement(new CrashJava());  

    System。out。println(v);  

  }  

} ///:~  

  

若只是简单地创建一个 CrashJava 对象,并将其打印出来,就会得到无穷无尽的一系列违例错误。然而,假 

如将CrashJava 对象置入一个Vector,并象这里演示的那样打印Vector,就不会出现什么错误提示,甚至连 

一个违例都不会出现。此时Java 只是简单地崩溃(但至少它没有崩溃我的操作系统)。这已在 Java 1。1 中 

测试通过。  

此时发生的是字串的自动类型转换。当我们使用下述语句时:  

〃CrashJava address: 〃 + this  

编译器就在一个字串后面发现了一个“+”以及好象并非字串的其他东西,所以它会试图将 this 转换成一个 

字串。转换时调用的是 toString(),后者会产生一个递归调用。若在一个Vector 内出现这种事情,看起来 

堆栈就会溢出,同时违例控制机制根本没有机会作出响应。  

若确实想在这种情况下打印出对象的地址,解决方案就是调用 Object 的 to
返回目录 上一页 下一页 回到顶部 1 1
快捷操作: 按键盘上方向键 ← 或 → 可快速上下翻页 按键盘上的 Enter 键可回到本书目录页 按键盘上方向键 ↑ 可回到本页顶部!
温馨提示: 温看小说的同时发表评论,说出自己的看法和其它小伙伴们分享也不错哦!发表书评还可以获得积分和经验奖励,认真写原创书评 被采纳为精评可以获得大量金币、积分和经验奖励哦!