友情提示:如果本网页打开太慢或显示不完整,请尝试鼠标右键“刷新”本网页!
Java编程思想第4版[中文版](PDF格式)-第13部分
快捷操作: 按键盘上方向键 ← 或 → 可快速上下翻页 按键盘上的 Enter 键可回到本书目录页 按键盘上方向键 ↑ 可回到本页顶部! 如果本书没有阅读完,想下次继续接着阅读,可使用上方 "收藏到我的浏览器" 功能 和 "加入书签" 功能!
传递(这在 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 违例控制:解决错误
从最古老的程序设计语言开始,错误控制一直都是设计者们需要解决的一个大问题。由于很难设计出一套完
美的错误控制方案,许多语言干脆将问题简单地忽略掉,将其转嫁给库设计人员。对大多数错误控制方案来
说,最主要的一个问题是它们严重依赖程序员的警觉性,而不是依赖语言本身的强制标准。如果程序员不够
警惕——若比较匆忙,这几乎是肯定会发生的——程序所依赖的错误控制方案便会失效。
“违例控制”将错误控制方案内置到程序设计语言中,有时甚至内建到操作系统内。这里的“违例”
(Exception)属于一个特殊的对象,它会从产生错误的地方“扔”或“掷”出来。随后,这个违例会被设计
用于控制特定类型错误的“违例控制器”捕获。在情况变得不对劲的时候,可能有几个违例控制器并行捕获
对应的违例对象。由于采用的是独立的执行路径,所以不会干扰我们的常规执行代码。这样便使代码的编写
变得更加简单,因为不必经常性强制检查代码。除此以外,“掷”出的一个违例不同于从函数返回的错误
值,也不同于由函数设置的一个标志。那些错误值或标志的作用是指示一个错误状态,是可以忽略的。但违
例不能被忽略,所以肯定能在某个地方得到处置。最后,利用违例能够可靠地从一个糟糕的环境中恢复。此
时一般不需要退出,我们可以采取某些处理,恢复程序的正常执行。显然,这样编制出来的程序显得更加可
靠。
Java 的违例控制机制与大多数程序设计语言都有所不同。因为在Java 中,违例控制模块是从一开始就封装
好的,所以必须使用它!如果没有自己写一些代码来正确地控制违例,就会得到一条编译期出错提示。这样
可保证程序的连贯性,使错误控制变得更加容易。
注意违例控制并不属于一种面向对象的特性,尽管在面向对象的程序设计语言中,违例通常是用一个对象表
示的。早在面向对象语言问世以前,违例控制就已经存在了。
36
…………………………………………………………Page 38……………………………………………………………
1。9 多线程
在计算机编程中,一个基本的概念就是同时对多个任务加以控制。许多程序设计问题都要求程序能够停下手
头的工作,改为处理其他一些问题,再返回主进程。可以通过多种途径达到这个目的。最开始的时候,那些
拥有机器低级知识的程序员编写一些“中断服务例程”,主进程的暂停是通过硬件级的中断实现的。尽管这
是一种有用的方法,但编出的程序很难移植,由此造成了另一类的代价高昂问题。
有些时候,中断对那些实时性很强的任务来说是很有必要的。但还存在其他许多问题,它们只要求将问题划
分进入独立运行的程序片断中,使整个程序能更迅速地响应用户的请求。在一个程序中,这些独立运行的片
断叫作“线程”(Thread),利用它编程的概念就叫作“多线程处理”。多线程处理一个常见的例子就是用
户界面。利用线程,用户可按下一个按钮,然后程序会立即作出响应,而不是让用户等待程序完成了当前任
务以后才开始响应。
最开始,线程只是用于分配单个处理器的处理时间的一种工具。但假如操作系统本身支持多个处理器,那么
每个线程都可分配给一个不同的处理器,真正进入“并行运算”状态。从程序设计语言的角度看,多线程操
作最有价值的特性之一就是程序员不必关心到底使用了多少个处理器。程序在逻辑意义上被分割为数个线
程;假如机器本身安装了多个处理器,那么程序会运行得更快,毋需作出任何特殊的调校。
根据前面的论述,大家可能感觉线程处理非常简单。但必须注意一个问题:共享资源!如果有多个线程同时
运行,而且它们试图访问相同的资源,就会遇到一个问题。举个例子来说,两个进程不能将信息同时发送给
一台打印机。为解决这个问题,对那些可共享的资源来说(比如打印机),它们在使用期间必须进入锁定状
态。所以一个线程可将资源锁定,在完成了它的任务后,再解开(释放)这个锁,使其他线程可以接着使用
同样的资源。
Java 的多线程机制已内建到语言中,这使一个可能较复杂的问题变得简单起来。对多线程处理的支持是在对
象这一级支持的,所以一个执行线程可表达为一个对象。Java 也提供了有限的资源锁定方案。它能锁定任何
对象占用的内存(内存实际是多种共享资源的一种),所以同一时间只能有一个线程使用特定的内存空间。
为达到这个目的,需要使用synchronized关键字。其他类型的资源必须由程序员明确锁定,这通常要求程序
员创建一个对象,用它代表一把锁,所有线程在访问那个资源时都必须检查这把锁。
1。10 永久性
创建一个对象后,只要我们需要,它就会一直存在下去。但在程序结束运行时,对象的“生存期”也会宣告
结束。尽管这一现象表面上非常合理,但深入追究就会发现,假如在程序停止运行以后,对象也能继续存
在,并能保留它的全部信息,那么在某些情况下将是一件非常有价值的事情。下次启动程序时,对象仍然在
那里,里面保留的信息仍然是程序上一次运行时的那些信息。当然,可以将信息写入一个文件或者数据库,
从而达到相同的效果。但尽管可将所有东西都看作一个对象,如果能将对象声明成“永久性”,并令其为我
们照看其他所有细节,无疑也是一件相当方便的事情。
Java 1。1 提供了对“有限永久性”的支持,这意味着我们可将对象简单地保存到磁盘上,以后任何时间都可
取回。之所以称它为“有限”的,是由于我们仍然需要明确发出调用,进行对象的保存和取回工作。这些工
作不能自动进行。在Java 未来的版本中,对“永久性”的支持有望更加全面。
1。11 Java 和因特网
既然Java 不过另一种类型的程序设计语言,大家可能会奇怪它为什么值得如此重视,为什么还有这么多的人
认为它是计算机程序设计的一个里程碑呢?如果您来自一个传统的程序设计背景,那么答案在刚开始的时候
并不是很明显。Java 除了可解决传统的程序设计问题以外,还能解决World Wide Web (万维网)上的编程问
题。
1。11。1 什么是 Web ?
Web 这个词刚开始显得有些泛泛,似乎“冲浪”、“网上存在”以及“主页”等等都和它拉上了一些关系。
甚至还有一种“Internet 综合症”的说法,对许多人狂热的上网行为提出了质疑。我们在这里有必要作一些
深入的探讨,但在这之前,必须理解客户机/服务器系统的概念,这是充斥着许多令人迷惑的问题的又一个
计算领域。
1。 客户机/服务器计算
客户机/服务器系统的基本思想是我们能在一个统一的地方集中存放信息资源。一般将数据集中保存在某个
37
…………………………………………………………Page 39……………………………………………………………
数据库中,根据其他人或者机器的请求将信息投递给对方。客户机/服务器概述的一个关键在于信息是“集
中存放”的。所以我们能方便地更改信息,然后将修改过的信息发放给信息的消费者。将各种元素集中到一
起,信息仓库、用于投递信息的软件以及信息及软件所在的那台机器,它们联合起来便叫作“服务器”
(Server)。而对那些驻留在远程机器上的软件,它们需要与服务器通信,取回信息,进行适当的处理,然
后在远程机器上显示出来,这些就叫作“客户” (Client)。
这样看来,客户机/服务器的基本概念并不复杂。这里要注意的一个主要问题是单个服务器需要同时向多个
客户提供服务。在这一机制中,通常少不了一套数据库管理系统,使设计人员能将数据布局封装到表格中,
以获得最优的使用。除此以外,系统经常允许客户将新信息插入一个服务器。这意味着必须确保客户的新数
据不会与其他客户的新数据冲突,或者说需要保证那些数据在加入数据库的时候不会丢失(用数据库的术语
来说,这叫作“事务处理”)。客户软件发生了改变之后,它们必须在客户机器上构建、调试以及安装。所
有这些会使问题变得比我们一般想象的复杂得多。另外,对多种类型的计算机和操作系统的支持也是一个大
问题。最后,性能的问题显得尤为重要:可能会有数百个客户同时向服务器发出请求。所以任何微小的延误
都是不能忽视的。为尽可能缓解潜伏的问题,程序员需要谨慎地分散任务的处理负担。一般可以考虑让客户
机负担部分处理任务,但有时亦可分派给服务器所在地的其他机器,那些机器亦叫作“中间件”(中间件也
用于改进对系统的维护)。
所以在具体实现的时候,其他人发布信息这样一个简单的概念可能变得异常复杂。有时甚至会使人产生完全
无从着手的感觉。客户机/服务器的概念在这时就可以大显身手了。事实上,大约有一半的程序设计活动都
可以采用客户机/服务器的结构。这种系统可负责从处理订单及信用卡交易,一直到发布各类数据的方方面
面的任务——股票市场、科学研究、政府运作等等。在过去,我们一般为单独的问题采取单独的解决方案;
每次都要设计一套新方案。这些方案无论创建还是使用都比较困难,用户每次都要学习和适应新界面。客户
机/服务器问题需要从根本上加以变革!
2。 Web 是一个巨大的服务器
Web 实际就是一套规模巨大的客户机/服务器系统。但它的情况要复杂一些,因为所有服务器和客户都同时
存在于单个网络上面。但我们没必要了解更进一步的细节,因为唯一要关心的就是一次建立同一个服务器的
连接,并同它打交道(即使可能要在全世界的范围内搜索正确的服务器)。
最开始的时候,这是一个简单的单向操作过程。我们向一个服务器发出请求,它向我们回传一个文件,由于
本机的浏览器软件(亦即“客户”或“客户程序”)负责解释和格式化,并在我们面前的屏幕上正确地显示
出来。但人们不久就不满足于只从一个服务器传递网页。他们希望获得完全的客户机/服务器能力,使客户
(程序)也能反馈一些信息到服务器。比如希望对服务器上的数据库进行检索,向服务器添加新信息,或者
下一份订单等等(这也提供了比以前的系统更高的安全要求)。在Web 的发展过程中,我们可以很清晰地看
出这些令人心喜的变化。
Web
快捷操作: 按键盘上方向键 ← 或 → 可快速上下翻页 按键盘上的 Enter 键可回到本书目录页 按键盘上方向键 ↑ 可回到本页顶部!
温馨提示: 温看小说的同时发表评论,说出自己的看法和其它小伙伴们分享也不错哦!发表书评还可以获得积分和经验奖励,认真写原创书评 被采纳为精评可以获得大量金币、积分和经验奖励哦!