按键盘上方向键 ← 或 → 可快速上下翻页,按键盘上的 Enter 键可回到本书目录页,按键盘上方向键 ↑ 可回到本页顶部!
————未阅读完?加入书签已便下次继续阅读!
8。2。1 缺点:类型未知
使用Java 集合的“缺点”是在将对象置入一个集合时丢失了类型信息。之所以会发生这种情况,是由于当初
编写集合时,那个集合的程序员根本不知道用户到底想把什么类型置入集合。若指示某个集合只允许特定的
类型,会妨碍它成为一个“常规用途”的工具,为用户带来麻烦。为解决这个问题,集合实际容纳的是类型
为Object 的一些对象的句柄。这种类型当然代表Java 中的所有对象,因为它是所有类的根。当然,也要注
意这并不包括基本数据类型,因为它们并不是从“任何东西”继承来的。这是一个很好的方案,只是不适用
213
…………………………………………………………Page 215……………………………………………………………
下述场合:
(1) 将一个对象句柄置入集合时,由于类型信息会被抛弃,所以任何类型的对象都可进入我们的集合——即
便特别指示它只能容纳特定类型的对象。举个例子来说,虽然指示它只能容纳猫,但事实上任何人都可以把
一条狗扔进来。
(2) 由于类型信息不复存在,所以集合能肯定的唯一事情就是自己容纳的是指向一个对象的句柄。正式使用
它之前,必须对其进行造型,使其具有正确的类型。
值得欣慰的是,Java 不允许人们滥用置入集合的对象。假如将一条狗扔进一个猫的集合,那么仍会将集合内
的所有东西都看作猫,所以在使用那条狗时会得到一个“违例”错误。在同样的意义上,假若试图将一条狗
的句柄“造型”到一只猫,那么运行期间仍会得到一个“违例”错误。
下面是个例子:
//: CatsAndDogs。java
// Simple collection example (Vector)
import java。util。*;
class Cat {
private int catNumber;
Cat(int i) {
catNumber = i;
}
void print() {
System。out。println(〃Cat #〃 + catNumber);
}
}
class Dog {
private int dogNumber;
Dog(int i) {
dogNumber = i;
}
void print() {
System。out。println(〃Dog #〃 + dogNumber);
}
}
public class CatsAndDogs {
public static void main(String'' args) {
Vector cats = new Vector();
for(int i = 0; i 《 7; i++)
cats。addElement(new Cat(i));
// Not a problem to add a dog to cats:
cats。addElement(new Dog(7));
for(int i = 0; i 《 cats。size(); i++)
((Cat)cats。elementAt(i))。print();
// Dog is detected only at run…time
}
} ///:~
可以看出,Vector 的使用是非常简单的:先创建一个,再用 addElement()置入对象,以后用 elementAt()取
得那些对象(注意Vector 有一个 size()方法,可使我们知道已添加了多少个元素,以便防止误超边界,造
成违例错误)。
214
…………………………………………………………Page 216……………………………………………………………
Cat 和 Dog 类都非常浅显——除了都是“对象”之外,它们并无特别之处(倘若不明确指出从什么类继承,
就默认为从 Object 继承。所以我们不仅能用Vector 方法将 Cat 对象置入这个集合,也能添加 Dog 对象,同
时不会在编译期和运行期得到任何出错提示。用Vector 方法 elementAt()获取原本认为是Cat 的对象时,实
际获得的是指向一个Object 的句柄,必须将那个对象造型为Cat。随后,需要将整个表达式用括号封闭起
来,在为Cat 调用print()方法之前进行强制造型;否则就会出现一个语法错误。在运行期间,如果试图将
Dog 对象造型为 Cat,就会得到一个违例。
这些处理的意义都非常深远。尽管显得有些麻烦,但却获得了安全上的保证。我们从此再难偶然造成一些隐
藏得深的错误。若程序的一个部分(或几个部分)将对象插入一个集合,但我们只是通过一次违例在程序的
某个部分发现一个错误的对象置入了集合,就必须找出插入错误的位置。当然,可通过检查代码达到这个目
的,但这或许是最笨的调试工具。另一方面,我们可从一些标准化的集合类开始自己的编程。尽管它们在功
能上存在一些不足,且显得有些笨拙,但却能保证没有隐藏的错误。
1。 错误有时并不显露出来
在某些情况下,程序似乎正确地工作,不造型回我们原来的类型。第一种情况是相当特殊的:String 类从编
译器获得了额外的帮助,使其能够正常工作。只要编译器期待的是一个String 对象,但它没有得到一个,就
会自动调用在Object 里定义、并且能够由任何Java 类覆盖的 toString()方法。这个方法能生成满足要求的
String 对象,然后在我们需要的时候使用。
因此,为了让自己类的对象能显示出来,要做的全部事情就是覆盖toString()方法,如下例所示:
//: WorksAnyway。java
// In special cases; things just seem
// to work correctly。
import java。util。*;
class Mouse {
private int mouseNumber;
Mouse(int i) {
mouseNumber = i;
}
// Magic method:
public String toString() {
return 〃This is Mouse #〃 + mouseNumber;
}
void print(String msg) {
if(msg != null) System。out。println(msg);
System。out。println(
〃Mouse number 〃 + mouseNumber);
}
}
class MouseTrap {
static void caughtYa(Object m) {
Mouse mouse = (Mouse)m; // Cast from Object
mouse。print(〃Caught one!〃);
}
}
public class WorksAnyway {
public static void main(String'' args) {
Vector mice = new Vector();
for(int i = 0; i 《 3; i++)
mice。addElement(new Mouse(i));
215
…………………………………………………………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());
就会在编译期间获得一条出错消息。采用这种方式,尽管从编码的角度看显得更令人沉闷,但可以立即判断
出是否使用了正确的类型。
注意在使用 elemen