友情提示:如果本网页打开太慢或显示不完整,请尝试鼠标右键“刷新”本网页!
Java编程思想第4版[中文版](PDF格式)-第55部分
快捷操作: 按键盘上方向键 ← 或 → 可快速上下翻页 按键盘上的 Enter 键可回到本书目录页 按键盘上方向键 ↑ 可回到本页顶部! 如果本书没有阅读完,想下次继续接着阅读,可使用上方 "收藏到我的浏览器" 功能 和 "加入书签" 功能!
初始化,全部工作在一条语句里完成。
下面这个表达式:
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 集合的“缺点”是在将对象置入一个集合时丢失了类型信息。之所以会发生这种情况,是由于当初
编写集合时,那个集合的程序员根本不知道用户到底想把什么类型置入集合。若指示某个集合只允许特定的
类型,会妨碍它成为一个“常规用途”的工具,为用户带来麻烦。为解决这个问题,集合实际容纳的是类型
为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
快捷操作: 按键盘上方向键 ← 或 → 可快速上下翻页 按键盘上的 Enter 键可回到本书目录页 按键盘上方向键 ↑ 可回到本页顶部!
温馨提示: 温看小说的同时发表评论,说出自己的看法和其它小伙伴们分享也不错哦!发表书评还可以获得积分和经验奖励,认真写原创书评 被采纳为精评可以获得大量金币、积分和经验奖励哦!