Java编程思想第4版[中文版](PDF格式)-第60部分
按键盘上方向键 ← 或 → 可快速上下翻页,按键盘上的 Enter 键可回到本书目录页,按键盘上方向键 ↑ 可回到本页顶部!
————未阅读完?加入书签已便下次继续阅读!
下面这个例子展示了对数组进行初始化的不同方式,以及如何将数组句柄分配给不同的数组对象。它也揭示
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 集合的“缺点”是在将对象置入一个集合时丢失了类型信息。之所以会发生这种情况,是由于当初