按键盘上方向键 ← 或 → 可快速上下翻页,按键盘上的 Enter 键可回到本书目录页,按键盘上方向键 ↑ 可回到本页顶部!
————未阅读完?加入书签已便下次继续阅读!
和在示意图中一样,MoreUseful (更有用的)对Useful (有用的)的接口进行了扩展。但由于它是继承来
的,所以也能上溯造型到一个Useful。我们可看到这会在对数组x (位于main()中)进行初始化的时候发
生。由于数组中的两个对象都属于 Useful 类,所以可将 f()和g()方法同时发给它们两个。而且假如试图调
用u() (它只存在于MoreUseful),就会收到一条编译期出错提示。
若想访问一个MoreUseful 对象的扩展接口,可试着进行下溯造型。如果它是正确的类型,这一行动就会成
功。否则,就会得到一个ClassCastException 。我们不必为这个违例编写任何特殊的代码,因为它指出的是
一个可能在程序中任何地方发生的一个编程错误。
RTTI 的意义远不仅仅反映在造型处理上。例如,在试图下溯造型之前,可通过一种方法了解自己处理的是什
么类型。整个第 11章都在讲述 Java 运行期类型标识的方方面面。
7。9 总结
“多形性”意味着“不同的形式”。在面向对象的程序设计中,我们有相同的外观(基础类的通用接口)以
及使用那个外观的不同形式:动态绑定或组织的、不同版本的方法。
通过这一章的学习,大家已知道假如不利用数据抽象以及继承技术,就不可能理解、甚至去创建多形性的一
个例子。多形性是一种不可独立应用的特性(就象一个 switch 语句),只可与其他元素协同使用。我们应将
其作为类总体关系的一部分来看待。人们经常混淆 Java 其他的、非面向对象的特性,比如方法过载等,这些
特性有时也具有面向对象的某些特征。但不要被愚弄:如果以后没有绑定,就不成其为多形性。
为使用多形性乃至面向对象的技术,特别是在自己的程序中,必须将自己的编程视野扩展到不仅包括单独一
个类的成员和消息,也要包括类与类之间的一致性以及它们的关系。尽管这要求学习时付出更多的精力,但
却是非常值得的,因为只有这样才可真正有效地加快自己的编程速度、更好地组织代码、更容易做出包容面
广的程序以及更易对自己的代码进行维护与扩展。
7。10 练习
(1) 创建Rodent (啮齿动物):Mouse (老鼠);Gerbil (鼹鼠);Hamster (大颊鼠)等的一个继承分级结
构。在基础类中,提供适用于所有 Rodent 的方法,并在衍生类中覆盖它们,从而根据不同类型的Rodent 采
取不同的行动。创建一个Rodent 数组,在其中填充不同类型的 Rodent,然后调用自己的基础类方法,看看
会有什么情况发生。
(2) 修改练习 1,使Rodent 成为一个接口。
(3) 改正WindError。java 中的问题。
(4) 在GreenhouseControls。java 中,添加Event 内部类,使其能打开和关闭风扇。
208
…………………………………………………………Page 210……………………………………………………………
第 8 章 对象的容纳
“如果一个程序只含有数量固定的对象,而且已知它们的存在时间,那么这个程序可以说是相当简单的。”
通常,我们的程序需要根据程序运行时才知道的一些标准创建新对象。若非程序正式运行,否则我们根本不
知道自己到底需要多少数量的对象,甚至不知道它们的准确类型。为了满足常规编程的需要,我们要求能在
任何时候、任何地点创建任意数量的对象。所以不可依赖一个已命名的句柄来容纳自己的每一个对象,就象
下面这样:
MyObject myHandle;
因为根本不知道自己实际需要多少这样的东西。
为解决这个非常关键的问题,Java 提供了容纳对象(或者对象的句柄)的多种方式。其中内建的类型是数
组,我们之前已讨论过它,本章准备加深大家对它的认识。此外,Java 的工具(实用程序)库提供了一些
“集合类”(亦称作“容器类”,但该术语已由AWT 使用,所以这里仍采用“集合”这一称呼)。利用这些
集合类,我们可以容纳乃至操纵自己的对象。本章的剩余部分会就此进行详细讨论。
8。1 数组
对数组的大多数必要的介绍已在第 4 章的最后一节进行。通过那里的学习,大家已知道自己该如何定义及初
始化一个数组。对象的容纳是本章的重点,而数组只是容纳对象的一种方式。但由于还有其他大量方法可容
纳数组,所以是哪些地方使数组显得如此特别呢?
有两方面的问题将数组与其他集合类型区分开来:效率和类型。对于Java 来说,为保存和访问一系列对象
(实际是对象的句柄)数组,最有效的方法莫过于数组。数组实际代表一个简单的线性序列,它使得元素的
访问速度非常快,但我们却要为这种速度付出代价:创建一个数组对象时,它的大小是固定的,而且不可在
那个数组对象的“存在时间”内发生改变。可创建特定大小的一个数组,然后假如用光了存储空间,就再创
建一个新数组,将所有句柄从旧数组移到新数组。这属于“矢量”(Vector)类的行为,本章稍后还会详细
讨论它。然而,由于为这种大小的灵活性要付出较大的代价,所以我们认为矢量的效率并没有数组高。
C++的矢量类知道自己容纳的是什么类型的对象,但同 Java 的数组相比,它却有一个明显的缺点:C++矢量类
的operator''不能进行范围检查,所以很容易超出边界(然而,它可以查询 vector 有多大,而且at()方法
确实能进行范围检查)。在Java 中,无论使用的是数组还是集合,都会进行范围检查——若超过边界,就会
获得一个RuntimeException (运行期违例)错误。正如大家在第9 章会学到的那样,这类违例指出的是一个
程序员错误,所以不需要在代码中检查它。在另一方面,由于 C++的vector 不进行范围检查,所以访问速度
较快——在 Java 中,由于对数组和集合都要进行范围检查,所以对性能有一定的影响。
本章还要学习另外几种常见的集合类:Vector (矢量)、Stack (堆栈)以及Hashtable (散列表)。这些类
都涉及对对象的处理——好象它们没有特定的类型。换言之,它们将其当作 Object 类型处理(Object 类型
是Java 中所有类的“根”类)。从某个角度看,这种处理方法是非常合理的:我们仅需构建一个集合,然后
任何Java 对象都可以进入那个集合(除基本数据类型外——可用Java 的基本类型封装类将其作为常数置入
集合,或者将其封装到自己的类内,作为可以变化的值使用)。这再一次反映了数组优于常规集合:创建一
个数组时,可令其容纳一种特定的类型。这意味着可进行编译期类型检查,预防自己设置了错误的类型,或
者错误指定了准备提取的类型。当然,在编译期或者运行期,Java 会防止我们将不当的消息发给一个对象。
所以我们不必考虑自己的哪种做法更加危险,只要编译器能及时地指出错误,同时在运行期间加快速度,目
的也就达到了。此外,用户很少会对一次违例事件感到非常惊讶的。
考虑到执行效率和类型检查,应尽可能地采用数组。然而,当我们试图解决一个更常规的问题时,数组的局
限也可能显得非常明显。在研究过数组以后,本章剩余的部分将把重点放到Java 提供的集合类身上。
8。1。1 数组和第一类对象
无论使用的数组属于什么类型,数组标识符实际都是指向真实对象的一个句柄。那些对象本身是在内存
“堆”里创建的。堆对象既可“隐式”创建(即默认产生),亦可“显式”创建(即明确指定,用一个new
表达式)。堆对象的一部分(实际是我们能访问的唯一字段或方法)是只读的 length (长度)成员,它告诉
我们那个数组对象里最多能容纳多少元素。对于数组对象,“''”语法是我们能采用的唯一另类访问方法。
下面这个例子展示了对数组进行初始化的不同方式,以及如何将数组句柄分配给不同的数组对象。它也揭示
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。l