按键盘上方向键 ← 或 → 可快速上下翻页,按键盘上的 Enter 键可回到本书目录页,按键盘上方向键 ↑ 可回到本页顶部!
————未阅读完?加入书签已便下次继续阅读!
for(int i = 0; i 《 a3。length; i++) {
119
…………………………………………………………Page 121……………………………………………………………
a3'i' = new int'pRand(5)''';
for(int j = 0; j 《 a3'i'。length; j++)
a3'i''j' = new int'pRand(5)';
}
for(int i = 0; i 《 a3。length; i++)
for(int j = 0; j 《 a3'i'。length ; j++)
for(int k = 0; k 《 a3'i''j'。length;
k++)
prt(〃a3'〃 + i + 〃''〃 +
j + 〃''〃 + k +
〃' = 〃 + a3'i''j''k');
// Array of non…primitive objects:
Integer'''' a4 = {
{ new Integer(1); new Integer(2)};
{ new Integer(3); new Integer(4)};
{ new Integer(5); new Integer(6)};
};
for(int i = 0; i 《 a4。length; i++)
for(int j = 0; j 《 a4'i'。length; j++)
prt(〃a4'〃 + i + 〃''〃 + j +
〃' = 〃 + a4'i''j');
Integer'''' a5;
a5 = new Integer'3''';
for(int i = 0; i 《 a5。length; i++) {
a5'i' = new Integer'3';
for(int j = 0; j 《 a5'i'。length; j++)
a5'i''j' = new Integer(i*j);
}
for(int i = 0; i 《 a5。length; i++)
for(int j = 0; j 《 a5'i'。length; j++)
prt(〃a5'〃 + i + 〃''〃 + j +
〃' = 〃 + a5'i''j');
}
static void prt(String s) {
System。out。println(s);
}
} ///:~
用于打印的代码里使用了 length,所以它不必依赖固定的数组大小。
第一个例子展示了基本数据类型的一个多维数组。我们可用花括号定出数组内每个矢量的边界:
int'''' a1 = {
{ 1; 2; 3; };
{ 4; 5; 6; };
};
每个方括号对都将我们移至数组的下一级。
第二个例子展示了用new 分配的一个三维数组。在这里,整个数组都是立即分配的:
int'''''' a2 = new int'2''2''4';
但第三个例子却向大家揭示出构成矩阵的每个矢量都可以有任意的长度:
int'''''' a3 = new int'pRand(7)''''';
120
…………………………………………………………Page 122……………………………………………………………
for(int i = 0; i 《 a3。length; i++) {
a3'i' = new int'pRand(5)''';
for(int j = 0; j 《 a3'i'。length; j++)
a3'i''j' = new int'pRand(5)';
}
对于第一个 new 创建的数组,它的第一个元素的长度是随机的,其他元素的长度则没有定义。for 循环内的
第二个 new 则会填写元素,但保持第三个索引的未定状态——直到碰到第三个new。
根据输出结果,大家可以看到:假若没有明确指定初始化值,数组值就会自动初始化成零。
可用类似的表式处理非基本类型对象的数组。这从第四个例子可以看出,它向我们演示了用花括号收集多个
new 表达式的能力:
Integer'''' a4 = {
{ new Integer(1); new Integer(2)};
{ new Integer(3); new Integer(4)};
{ new Integer(5); new Integer(6)};
};
第五个例子展示了如何逐渐构建非基本类型的对象数组:
Integer'''' a5;
a5 = new Integer'3''';
for(int i = 0; i 《 a5。length; i++) {
a5'i' = new Integer'3';
for(int j = 0; j 《 a5'i'。length; j++)
a5'i''j' = new Integer(i*j);
}
i*j 只是在 Integer里置了一个有趣的值。
4。6 总结
作为初始化的一种具体操作形式,构建器应使大家明确感受到在语言中进行初始化的重要性。与 C++的程序
设计一样,判断一个程序效率如何,关键是看是否由于变量的初始化不正确而造成了严重的编程错误(臭
虫)。这些形式的错误很难发现,而且类似的问题也适用于不正确的清除或收尾工作。由于构建器使我们能
保证正确的初始化和清除(若没有正确的构建器调用,编译器不允许对象创建),所以能获得完全的控制权
和安全性。
在C++中,与“构建”相反的“破坏”(Destruction)工作也是相当重要的,因为用new 创建的对象必须明
确地清除。在Java 中,垃圾收集器会自动为所有对象释放内存,所以 Java 中等价的清除方法并不是经常都
需要用到的。如果不需要类似于构建器的行为,Java 的垃圾收集器可以极大简化编程工作,而且在内存的管
理过程中增加更大的安全性。有些垃圾收集器甚至能清除其他资源,比如图形和文件句柄等。然而,垃圾收
集器确实也增加了运行期的开销。但这种开销到底造成了多大的影响却是很难看出的,因为到目前为止,
Java 解释器的总体运行速度仍然是比较慢的。随着这一情况的改观,我们应该能判断出垃圾收集器的开销是
否使Java 不适合做一些特定的工作(其中一个问题是垃圾收集器不可预测的性质)。
由于所有对象都肯定能获得正确的构建,所以同这儿讲述的情况相比,构建器实际做的事情还要多得多。特
别地,当我们通过“创作”或“继承”生成新类的时候,对构建的保证仍然有效,而且需要一些附加的语法
来提供对它的支持。大家将在以后的章节里详细了解创作、继承以及它们对构建器造成的影响。
4。7 练习
(1) 用默认构建器创建一个类(没有自变量),用它打印一条消息。创建属于这个类的一个对象。
(2) 在练习 1 的基础上增加一个过载的构建器,令其采用一个String 自变量,并随同自己的消息打印出来。
(3) 以练习2 创建的类为基础上,创建属于它的对象句柄的一个数组,但不要实际创建对象并分配到数组
121
…………………………………………………………Page 123……………………………………………………………
里。运行程序时,注意是否打印出来自构建器调用的初始化消息。
(4) 创建同句柄数组联系起来的对象,最终完成练习3。
(5) 用自变量“before”,“after”和“none ”运行程序,试验Garbage。java。重复这个操作,观察是否
从输出中看出了一些固定的模式。改变代码,使System。runFinalization()在System。gc()之前调用,再观
察结果。
122
…………………………………………………………Page 124……………………………………………………………
第 5 章 隐藏实施过程
“进行面向对象的设计时,一项基本的考虑是:如何将发生变化的东西与保持不变的东西分隔开。”
这一点对于库来说是特别重要的。那个库的用户(客户程序员)必须能依赖自己使用的那一部分,并知道一
旦新版本的库出台,自己不需要改写代码。而与此相反,库的创建者必须能自由地进行修改与改进,同时保
证客户程序员代码不会受到那些变动的影响。
为达到这个目的,需遵守一定的约定或规则。例如,库程序员在修改库内的一个类时,必须保证不删除已有
的方法,因为那样做会造成客户程序员代码出现断点。然而,相反的情况却是令人痛苦的。对于一个数据成
员,库的创建者怎样才能知道哪些数据成员已受到客户程序员的访问呢?若方法属于某个类唯一的一部分,
而且并不一定由客户程序员直接使用,那么这种痛苦的情况同样是真实的。如果库的创建者想删除一种旧有
的实施方案,并置入新代码,此时又该怎么办呢?对那些成员进行的任何改动都可能中断客户程序员的代
码。所以库创建者处在一个尴尬的境地,似乎根本动弹不得。
为解决这个问题,Java 推出了“访问指示符”的概念,允许库创建者声明哪些东西是客户程序员可以使用
的,哪些是不可使用的。这种访问控制的级别在“最大访问”和“最小访问”的范围之间,分别包括:
public,“友好的”(无关键字),protected 以及private。根据前一段的描述,大家或许已总结出作为一
名库设计者,应将所有东西都尽可能保持为“private”(私有),并只展示出那些想让客户程序员使用的方
法。这种思路是完全正确的,尽管它有点儿违背那些用其他语言(特别是 C)编程的人的直觉,那些人习惯
于在没有任何限制的情况下访问所有东西。到这一章结束时,大家应该可以深刻体会到Java 访问控制的价
值。
然而,组件库以及控制谁能访问那个库的组件的概念现在仍不是完整的。仍存在这样一个问题:如何将组件
绑定到单独一个统一的库单元里。这是通过Java 的package (打包)关键字来实现的,而且访问指示符要受
到类在相同的包还是在不同的包里的影响。所以在本章的开头,大家首先要学习库组件如何置入包里。这样
才能理解访问指示符的完整含义。
5。1 包:库单元
我们用 import 关键字导入一个完整的库时,就会获得“包”(Package)。例如:
import java。util。*;
它的作用是导入完整的实用工具(Utility)库,该库属于标准Java 开发工具包的一部分。由于Vector 位于
java。util 里,所以现在要么指定完整名称“java。util。Vector”(可省略 import 语句),要么简单地指定
一个“Vector”(因为 import是默认的)。
若想导入单独一个类,可在 import语句里指定那个类的名字:
import java。util。Vector;
现在,我们可以自由地使用Vector。然而,java。util 中的其他任何类仍是不可使用的。
之所以要进行这样的导入,是为了提供一种特殊的机制,以便管理“命名空间”(Name Space)。我们所有
类成员的名字相互间都会隔离起来。位于类A 内的一个方法f()不会与位于类B