友情提示:如果本网页打开太慢或显示不完整,请尝试鼠标右键“刷新”本网页!
第三电子书 返回本书目录 加入书签 我的书架 我的书签 TXT全本下载 『收藏到我的浏览器』

Java编程思想第4版[中文版](PDF格式)-第17部分

快捷操作: 按键盘上方向键 ← 或 → 可快速上下翻页 按键盘上的 Enter 键可回到本书目录页 按键盘上方向键 ↑ 可回到本页顶部! 如果本书没有阅读完,想下次继续接着阅读,可使用上方 "收藏到我的浏览器" 功能 和 "加入书签" 功能!


有元素的访问方式;一个链接列表则用于保证所有元素的插入统一。所以我们能根据自己的需要选择适当的 

类型。其中包括集、队列、散列表、树、堆栈等等。  

所有集合都提供了相应的读写功能。将某样东西置入集合时,采用的方式是十分明显的。有一个叫作“推” 

 (Push )、“添加”(Add)或其他类似名字的函数用于做这件事情。但将数据从集合中取出的时候,方式却 

并不总是那么明显。如果是一个数组形式的实体,比如一个矢量(Vector),那么也许能用索引运算符或函 

数。但在许多情况下,这样做往往会无功而返。此外,单选定函数的功能是非常有限的。如果想对集合中的 

一系列元素进行操纵或比较,而不是仅仅面向一个,这时又该怎么办呢?  

办法就是使用一个“继续器”(Iterator),它属于一种对象,负责选择集合内的元素,并把它们提供给继 

承器的用户。作为一个类,它也提供了一级抽象。利用这一级抽象,可将集合细节与用于访问那个集合的代 

码隔离开。通过继承器的作用,集合被抽象成一个简单的序列。继承器允许我们遍历那个序列,同时毋需关 

心基础结构是什么——换言之,不管它是一个矢量、一个链接列表、一个堆栈,还是其他什么东西。这样一 

来,我们就可以灵活地改变基础数据,不会对程序里的代码造成干扰。Java 最开始(在 1。0和 1。1版中)提 

供的是一个标准继承器,名为 Enumeration (枚举),为它的所有集合类提供服务。Java 1。2 新增一个更复 

杂的集合库,其中包含了一个名为 Iterator 的继承器,可以做比老式的Enumeration 更多的事情。  

从设计角度出发,我们需要的是一个全功能的序列。通过对它的操纵,应该能解决自己的问题。如果一种类 

型的序列即可满足我们的所有要求,那么完全没有必要再换用不同的类型。有两方面的原因促使我们需要对 

集合作出选择。首先,集合提供了不同的接口类型以及外部行为。堆栈的接口与行为与队列的不同,而队列 

的接口与行为又与一个集(Set)或列表的不同。利用这个特征,我们解决问题时便有更大的灵活性。  

其次,不同的集合在进行特定操作时往往有不同的效率。最好的例子便是矢量(Vector)和列表(List )的 

区别。它们都属于简单的序列,拥有完全一致的接口和外部行为。但在执行一些特定的任务时,需要的开销 

却是完全不同的。对矢量内的元素进行的随机访问(存取)是一种常时操作;无论我们选择的选择是什么, 

需要的时间量都是相同的。但在一个链接列表中,若想到处移动,并随机挑选一个元素,就需付出“惨重” 

的代价。而且假设某个元素位于列表较远的地方,找到它所需的时间也会长许多。但在另一方面,如果想在 

序列中部插入一个元素,用列表就比用矢量划算得多。这些以及其他操作都有不同的执行效率,具体取决于 

序列的基础结构是什么。在设计阶段,我们可以先从一个列表开始。最后调整性能的时候,再根据情况把它 

换成矢量。由于抽象是通过继承器进行的,所以能在两者方便地切换,对代码的影响则显得微不足道。  

最后,记住集合只是一个用来放置对象的储藏所。如果那个储藏所能满足我们的所有需要,就完全没必要关 

心它具体是如何实现的(这是大多数类型对象的一个基本概念)。如果在一个编程环境中工作,它由于其他 

因素(比如在Windows 下运行,或者由垃圾收集器带来了开销)产生了内在的开销,那么矢量和链接列表之 

间在系统开销上的差异就或许不是一个大问题。我们可能只需要一种类型的序列。甚至可以想象有一个“完 

美”的集合抽象,它能根据自己的使用方式自动改变基层的实现方式。  



1。7。2 单根结构  



在面向对象的程序设计中,由于C++的引入而显得尤为突出的一个问题是:所有类最终是否都应从单独一个 

基础类继承。在Java 中(与其他几乎所有OOP 语言一样),对这个问题的答案都是肯定的,而且这个终级基 

础类的名字很简单,就是一个“Object”。这种“单根结构”具有许多方面的优点。  

单根结构中的所有对象都有一个通用接口,所以它们最终都属于相同的类型。另一种方案(就象 C++那样) 

是我们不能保证所有东西都属于相同的基本类型。从向后兼容的角度看,这一方案可与C 模型更好地配合, 

而且可以认为它的限制更少一些。但假期我们想进行纯粹的面向对象编程,那么必须构建自己的结构,以期 

获得与内建到其他 OOP 语言里的同样的便利。需添加我们要用到的各种新类库,还要使用另一些不兼容的接 

口。理所当然地,这也需要付出额外的精力使新接口与自己的设计方案配合(可能还需要多重继承)。为得 

到C++额外的“灵活性”,付出这样的代价值得吗?当然,如果真的需要——如果早已是 C 专家,如果对C 

有难舍的情结——那么就真的很值得。但假如你是一名新手,首次接触这类设计,象Java 那样的替换方案也 

许会更省事一些。  

单根结构中的所有对象(比如所有 Java 对象)都可以保证拥有一些特定的功能。在自己的系统中,我们知道 

对每个对象都能进行一些基本操作。一个单根结构,加上所有对象都在内存堆中创建,可以极大简化参数的 

传递(这在 C++里是一个复杂的概念)。  

利用单根结构,我们可以更方便地实现一个垃圾收集器。与此有关的必要支持可安装于基础类中,而垃圾收 

集器可将适当的消息发给系统内的任何对象。如果没有这种单根结构,而且系统通过一个句柄来操纵对象, 

那么实现垃圾收集器的途径会有很大的不同,而且会面临许多障碍。  

由于运行期的类型信息肯定存在于所有对象中,所以永远不会遇到判断不出一个对象的类型的情况。这对系 



                                                            34 


…………………………………………………………Page 36……………………………………………………………

统级的操作来说显得特别重要,比如违例控制;而且也能在程序设计时获得更大的灵活性。  

但大家也可能产生疑问,既然你把好处说得这么天花乱坠,为什么C++没有采用单根结构呢?事实上,这是 

早期在效率与控制上权衡的一种结果。单根结构会带来程序设计上的一些限制。而且更重要的是,它加大了 

新程序与原有C 代码兼容的难度。尽管这些限制仅在特定的场合会真的造成问题,但为了获得最大的灵活程 

度,C++最终决定放弃采用单根结构这一做法。而 Java 不存在上述的问题,它是全新设计的一种语言,不必 

与现有的语言保持所谓的“向后兼容”。所以很自然地,与其他大多数面向对象的程序设计语言一样,单根 

结构在Java 的设计方案中很快就落实下来。  



1。7。3 集合库与方便使用集合  



由于集合是我们经常都要用到的一种工具,所以一个集合库是十分必要的,它应该可以方便地重复使用。这 

样一来,我们就可以方便地取用各种集合,将其插入自己的程序。Java 提供了这样的一个库,尽管它在Java  

1。0和 1。1 中都显得非常有限(Java 1。2 的集合库则无疑是一个杰作)。  

  

1。 下溯造型与模板/通用性  

为了使这些集合能够重复使用,或者“再生”,Java 提供了一种通用类型,以前曾把它叫作“Object”。单 

根结构意味着、所有东西归根结底都是一个对象”!所以容纳了Object 的一个集合实际可以容纳任何东西。 

这使我们对它的重复使用变得非常简便。  

为使用这样的一个集合,只需添加指向它的对象句柄即可,以后可以通过句柄重新使用对象。但由于集合只 

能容纳Object,所以在我们向集合里添加对象句柄时,它会上溯造型成 Object,这样便丢失了它的身份或者 

标识信息。再次使用它的时候,会得到一个Object 句柄,而非指向我们早先置入的那个类型的句柄。所以怎 

样才能归还它的本来面貌,调用早先置入集合的那个对象的有用接口呢?  

在这里,我们再次用到了造型(Cast )。但这一次不是在分级结构中上溯造型成一种更“通用”的类型。而 

是下溯造型成一种更“特殊”的类型。这种造型方法叫作“下溯造型”(Downcasting)。举个例子来说,我 

们知道在上溯造型的时候,Circle (圆)属于Shape (几何形状)的一种类型,所以上溯造型是安全的。但 

我们不知道一个Object 到底是 Circle 还是Shape,所以很难保证下溯造型的安全进行,除非确切地知道自 

己要操作的是什么。  

但这也不是绝对危险的,因为假如下溯造型成错误的东西,会得到我们称为“违例”(Exception)的一种运 

行期错误。我们稍后即会对此进行解释。但在从一个集合提取对象句柄时,必须用某种方式准确地记住它们 

是什么,以保证下溯造型的正确进行。  

下溯造型和运行期检查都要求花额外的时间来运行程序,而且程序员必须付出额外的精力。既然如此,我们 

能不能创建一个“智能”集合,令其知道自己容纳的类型呢?这样做可消除下溯造型的必要以及潜在的错 

误。答案是肯定的,我们可以采用“参数化类型”,它们是编译器能自动定制的类,可与特定的类型配合。 

例如,通过使用一个参数化集合,编译器可对那个集合进行定制,使其只接受Shape,而且只提取Shape。  

参数化类型是C++一个重要的组成部分,这部分是C++没有单根结构的缘故。在 C++中,用于实现参数化类型 

的关键字是 template (模板)。Java 目前尚未提供参数化类型,因为由于使用的是单根结构,所以使用它显 

得有些笨拙。但这并不能保证以后的版本不会实现,因为“generic”这个词已被Java “保留到将来实现” 

 (在Ada 语言中,“generic”被用来实现它的模板)。Java 采取的这种关键字保留机制其实经常让人摸不 

着头脑,很难断定以后会发生什么事情。  



1。7。4 清除时的困境:由谁负责清除?  



每个对象都要求资源才能“生存”,其中最令人注目的资源是内存。如果不再需要使用一个对象,就必须将 

其清除,以便释放这些资源,以便其他对象使用。如果要解决的是非常简单的问题,如何清除对象这个问题 

并不显得很突出:我们创建对象,在需要的时候调用它,然后将其清除或者“破坏”。但在另一方面,我们 

平时遇到的问题往往要比这复杂得多。  

举个例子来说,假设我们要设计一套系统,用它管理一个机场的空中交通(同样的模型也可能适于管理一个 

仓库的货柜、或者一套影带出租系统、或者宠物店的宠物房。这初看似乎十分简单:构造一个集合用来容纳 

飞机,然后创建一架新飞机,将其置入集合。对进入空中交通管制区的所有飞机都如此处理。至于清除,在 

一架飞机离开这个区域的时候把它简单地删去即可。  

但事情并没有这么简单,可能还需要另一套系统来记录与飞机有关的数据。当然,和控制器的主要功能不 

同,这些数据的重要性可能一开始并不显露出来。例如,这条记录反映的可能是离开机场的所有小飞机的飞 

行计划。所以我们得到了由小飞机组成的另一个集合。一旦创建了一个飞机对象,如果它是一架小飞机,那 



                                                            35 


…………………………………………………………Page 37……………………………………………………………

么也必须把它置入这个集合。然后在系统空闲时期,需对这个集合中的对象进行一些后台处理。  

问题现在显得更复杂了:如何才能知道什么时间删除对象呢?用完对象后,系统的其他某些部分可能仍然要 

发挥作用。同样的问题也会在其他大量场合出现,而且在程序设计系统中(如C++),在用完一个对象之后 

必须明确地将其删除,所以问题会变得异常复杂(注释⑥)。  

  

⑥:注意这一点只对内存堆里创建的对象成立(用 new 命令创建的)。但在另一方面,对这儿描述的问题以 

及其他所有常见的编程问题来说,都要求对象在内存堆里创建。  

  

在Java 中,垃圾收集器在设计时已考虑到了内存的释放问题(尽管这并不包括清除一个对象涉及到的其他方 

面)。垃圾收集器“知道”一个对象在什么时候不再使用,然后会自动释放那个对象占据的内存空间。采用 

这种方式,另外加上所有对象都从单个根类Object 继承的事实,而且由于我们只能在内存堆中以一种方式创 

建对象,所以Java 的编程要比 C++的编程简单得多。我们只需要作出少量的抉择,即可克服原先存在的大量 

障碍。  

  

1。 垃圾收集器对效率及灵活性的影响  

既然这是如此好的一种手段,为什么在C++里没有得到充分的发挥呢?我们当然要为这种编程的方便性付出 

一定的代价,代价就是运行期的开销。正如早先提到的那样,在C++中,我们可在堆栈中创建对象。在这种 

情况下,对象会得以自动清除(但不具有在运行期间随心所欲创建对象的灵活性)。在堆栈中创建对象是为 

对象分配存储空间最有效的一种方式,也是释放那些空间最有效的一种方式。在内存堆(Heap )中创建对象 

可能要付出昂贵得多的代价。如果总是从同一个基础类继承,并使所有函数调用都具有“同质多形”特征, 

那么也不可避免地需要付出一定的代价。但垃圾收集器是一种特殊的问题,因为我们永远不能确定它什么时 

候启动或者要花多长的时间。这意味着在 Java 程序执行期间,存在着一种不连贯的因素。所以在某些特殊的 

场合,我们必须避免用它——比如在一个程序的执行必须保持稳定、连贯的时候(通常把它们叫作“实时程 

序”,尽管并不是所有实时编程问题都要这方面的要求——注释⑦)。  

  

⑦:根据本书一些技术性读者的反馈,有一个现成的实时 Java 系统(newmonics。)确实能够保证垃 

圾收集器的效能。  

  

C++语言的设计者曾经向C 程序员发出请求(而且做得非常成功),不要希望在可以使用 C 的任何地方,向语 

言里加入可能对C++的速度或使用造成影响的任何特性。这个目的达到了,但代价就是C++的编程不可避免地 

复杂起来。Java 比C++简单,但付出的代价是效率以及一定程度的灵活性。但对大多数程序设计问题来说, 

Java 无疑都应是我们的首选。  



1。8 违例控制:解决错误  



从最古老的程序设计语言开始,错误控制一直都是设计者们需要解决的一个大问题。由
返回目录 上一页 下一页 回到顶部 1 1
快捷操作: 按键盘上方向键 ← 或 → 可快速上下翻页 按键盘上的 Enter 键可回到本书目录页 按键盘上方向键 ↑ 可回到本页顶部!
温馨提示: 温看小说的同时发表评论,说出自己的看法和其它小伙伴们分享也不错哦!发表书评还可以获得积分和经验奖励,认真写原创书评 被采纳为精评可以获得大量金币、积分和经验奖励哦!