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

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

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



        〃in。close() unsuccessful〃);  

    }  

  }  



                                                                                          281 


…………………………………………………………Page 283……………………………………………………………

}  

  

public class Cleanup {  

  public static void main(String'' args) {  

    try {  

      InputFile in =   

        new InputFile(〃Cleanup。java〃);  

      String s;  

      int i = 1;  

      while((s = in。getLine()) != null)  

        System。out。println(〃〃+ i++ + 〃: 〃 + s);  

      in。cleanup();  

    } catch(Exception e) {  

      System。out。println(  

        〃Caught in main; e。printStackTrace()〃);  

      e。printStackTrace();  

    }  

  }  

} ///:~  

  

该例使用了 Java 1。1 IO 类。  

用于 InputFile 的构建器采用了一个String (字串)参数,它代表我们想打开的那个文件的名字。在一个 

try 块内部,它用该文件名创建了一个 FileReader。对FileReader 来说,除非转移并用它创建一个能够实际 

与之“交谈”的BufferedReader,否则便没什么用处。注意InputFile 的一个好处就是它同时合并了这两种 

行动。  

若FileReader 构建器不成功,就会产生一个 FileNotFoundException (文件未找到违例)。必须单独捕获这 

个违例——这属于我们不想关闭文件的一种特殊情况,因为文件尚未成功打开。其他任何捕获从句(catch) 

都必须关闭文件,因为文件已在进入那些捕获从句时打开(当然,如果多个方法都能产生一个 

FileNotFoundException 违例,就需要稍微用一些技巧。此时,我们可将不同的情况分隔到数个try 块 

内)。close()方法会掷出一个尝试过的违例。即使它在另一个catch 从句的代码块内,该违例也会得以捕 

获——对Java 编译器来说,那个 catch 从句不过是另一对花括号而已。执行完本地操作后,违例会被重新 

 “掷”出。这样做是必要的,因为这个构建器的执行已经失败,我们不希望调用方法来假设对象已正确创建 

以及有效。  

在这个例子中,没有采用前述的标志技术,finally 从句显然不是关闭文件的正确地方,因为这可能在每次 

构建器结束的时候关闭它。由于我们希望文件在 InputFile 对象处于活动状态时一直保持打开状态,所以这 

样做并不恰当。  

getLine()方法会返回一个字串,其中包含了文件中下一行的内容。它调用了readLine(),后者可能产生一 

个违例,但那个违例会被捕获,使 getLine()不会再产生任何违例。对违例来说,一项特别的设计问题是决 

定在这一级完全控制一个违例,还是进行部分控制,并传递相同(或不同)的违例,或者只是简单地传递 

它。在适当的时候,简单地传递可极大简化我们的编码工作。getLine()方法会变成:  

String getLine() throws IOException {  

return in。readLine();  

}  

但是当然,调用者现在需要对可能产生的任何 IOException进行控制。  

用户使用完毕 InputFile 对象后,必须调用 cleanup()方法,以便释放由 BufferedReader 以及/或者 

FileReader 占用的系统资源(如文件句柄)——注释⑥。除非 InputFile 对象使用完毕,而且到了需要弃之 

不用的时候,否则不应进行清除。大家可能想把这样的机制置入一个 finalize()方法内,但正如第 4 章指出 

的那样,并非总能保证 finalize()获得正确的调用(即便确定它会调用,也不知道何时开始)。这属于 Java 

的一项缺陷——除内存清除之外的所有清除都不会自动进行,所以必须知会客户程序员,告诉他们有责任用 

finalize()保证清除工作的正确进行。  

  

⑥:在 C++里,“破坏器”可帮我们控制这一局面。  



                                                                                   282 


…………………………………………………………Page 284……………………………………………………………

  

在Cleanup。java 中,我们创建了一个InputFile,用它打开用于创建程序的相同的源文件。同时一次读取该 

文件的一行内容,而且添加相应的行号。所有违例都会在 main()中被捕获——尽管我们可选择更大的可靠 

性。  

这个示例也向大家展示了为何在本书的这个地方引入违例的概念。违例与 Java 的编程具有很高的集成度,这 

主要是由于编译器会强制它们。只有知道了如何操作那些违例,才可更进一步地掌握编译器的知识。  



9。8 违例匹配  



 “掷”出一个违例后,违例控制系统会按当初编写的顺序搜索“最接近”的控制器。一旦找到相符的控制 

器,就认为违例已得到控制,不再进行更多的搜索工作。  

在违例和它的控制器之间,并不需要非常精确的匹配。一个衍生类对象可与基础类的一个控制器相配,如下 

例所示:  

  

//: Human。java  

// Catching Exception Hierarchies  

  

class Annoyance extends Exception {}  

class Sneeze extends Annoyance {}  

  

public class Human {  

  public static void main(String '' args) {  

    try {  

      throw new Sneeze();  

    } catch(Sneeze s) {  

      System。out。println(〃Caught Sneeze〃);  

    } catch(Annoyance a) {  

      System。out。println(〃Caught Annoyance〃);  

    }  

  }  

} ///:~  

  

Sneeze 违例会被相符的第一个 catch 从句捕获。当然,这只是第一个。然而,假如我们删除第一个 catch 从 

句:  

  

    try {  

      throw new Sneeze();  

    } catch(Annoyance a) {  

      System。out。println(〃Caught Annoyance〃);  

    }  

  

那么剩下的 catch 从句依然能够工作,因为它捕获的是 Sneeze 的基础类。换言之,catch(Annoyance e)能捕 

获一个Annoyance 以及从它衍生的任何类。这一点非常重要,因为一旦我们决定为一个方法添加更多的违 

例,而且它们都是从相同的基础类继承的,那么客户程序员的代码就不需要更改。至少能够假定它们捕获的 

是基础类。  

若将基础类捕获从句置于第一位,试图“屏蔽”衍生类违例,就象下面这样:  

  

    try {  

      throw new Sneeze();  

    } catch(Annoyance a) {  

      System。out。println(〃Caught Annoyance〃);  

    } catch(Sneeze s) {  

      System。out。println(〃Caught Sneeze〃);  



                                                                                          283 


…………………………………………………………Page 285……………………………………………………………

    }  

  

则编译器会产生一条出错消息,因为它发现永远不可能抵达Sneeze 捕获从句。  



9。8。1  违例准则  



用违例做下面这些事情:  

(1) 解决问题并再次调用造成违例的方法。  

(2) 平息事态的发展,并在不重新尝试方法的前提下继续。  

(3) 计算另一些结果,而不是希望方法产生的结果。  

(4) 在当前环境中尽可能解决问题,以及将相同的违例重新“掷”出一个更高级的环境。  

(5) 在当前环境中尽可能解决问题,以及将不同的违例重新“掷”出一个更高级的环境。  

(6) 中止程序执行。  

(7) 简化编码。若违例方案使事情变得更加复杂,那就会令人非常烦恼,不如不用。  

(8) 使自己的库和程序变得更加安全。这既是一种“短期投资”(便于调试),也是一种“长期投资”(改 

善应用程序的健壮性)  



9。9 总结  



通过先进的错误纠正与恢复机制,我们可以有效地增强代码的健壮程度。对我们编写的每个程序来说,错误 

恢复都属于一个基本的考虑目标。它在Java 中显得尤为重要,因为该语言的一个目标就是创建不同的程序组 

件,以便其他用户(客户程序员)使用。为构建一套健壮的系统,每个组件都必须非常健壮。  

在Java 里,违例控制的目的是使用尽可能精简的代码创建大型、可靠的应用程序,同时排除程序里那些不能 

控制的错误。  

违例的概念很难掌握。但只有很好地运用它,才可使自己的项目立即获得显著的收益。Java 强迫遵守违例所 

有方面的问题,所以无论库设计者还是客户程序员,都能够连续一致地使用它。  



9。10 练习  



(1) 用main()创建一个类,令其掷出 try 块内的 Exception 类的一个对象。为 Exception 的构建器赋予一个 

字串参数。在catch 从句内捕获违例,并打印出字串参数。添加一个finally 从句,并打印一条消息,证明 

自己真正到达那里。  

(2) 用 extends 关键字创建自己的违例类。为这个类写一个构建器,令其采用String 参数,并随同 String 

句柄把它保存到对象内。写一个方法,令其打印出保存下来的 String。创建一个 try…catch 从句,练习实际 

操作新违例。  

(3) 写一个类,并令一个方法掷出在练习2 中创建的类型的一个违例。试着在没有违例规范的前提下编译 

它,观察编译器会报告什么。接着添加适当的违例规范。在一个try…catch 从句中尝试自己的类以及它的违 

例。  

(4) 在第 5 章,找到调用了Assert。java 的两个程序,并修改它们,令其掷出自己的违例类型,而不是打印 

到System。err。该违例应是扩展了RuntimeException 的一个内部类。  



                                                                  284 


…………………………………………………………Page 286……………………………………………………………

                        第 10 章 Java IO 系统  



  

 “对语言设计人员来说,创建好的输入/输出系统是一项特别困难的任务。”  

  

由于存在大量不同的设计方案,所以该任务的困难性是很容易证明的。其中最大的挑战似乎是如何覆盖所有 

可能的因素。不仅有三种不同的种类的 IO 需要考虑(文件、控制台、网络连接),而且需要通过大量不同的 

方式与它们通信(顺序、随机访问、二进制、字符、按行、按字等等)。  

Java 库的设计者通过创建大量类来攻克这个难题。事实上,Java 的 IO系统采用了如此多的类,以致刚开始 

会产生不知从何处入手的感觉(具有讽刺意味的是,Java 的 IO设计初衷实际要求避免过多的类)。从Java  

1。0升级到 Java 1。1 后,IO 库的设计也发生了显著的变化。此时并非简单地用新库替换旧库,Sun 的设计人 

员对原来的库进行了大手笔的扩展,添加了大量新的内容。因此,我们有时不得不混合使用新库与旧库,产 

生令人无奈的复杂代码。  

本章将帮助大家理解标准Java 库内的各种 IO 类,并学习如何使用它们。本章的第一部分将介绍“旧”的 

Java 1。0 IO 流库,因为现在有大量代码仍在使用那个库。本章剩下的部分将为大家引入Java 1。1 IO 库的 

一些新特性。注意若用 Java 1。1 编译器来编译本章第一部分介绍的部分代码,可能会得到一条“不建议使用 

该特性”(Deprecated feature )警告消息。代码仍然能够使用;编译器只是建议我们换用本章后面要讲述 

的一些新特性。但我们这样做是有价值的,因为可以更清楚地认识老方法与新方法之间的一些差异,从而加 

深我们的理解(并可顺利阅读为Java 1。0 写的代码)。  



10。1 输入和输出  



可将Java 库的 IO类分割为输入与输出两个部分,这一点在用 Web 浏览器阅读联机Java 类文档时便可知道。 

通过继承,从 InputStream (输入流)衍生的所有类都拥有名为read()的基本方法,用于读取单个字节或者 

字节数组。类似地,从 OutputStream 衍生的所有类都拥有基本方法 write(),用于写入单个字节或者字节数 

组。然而,我们通常不会用到这些方法;它们之所以存在,是因为更复杂的类可以利用它们,以便提供一个 

更有用的接口。因此,我们很少用单个类创建自己的系统对象。一般情况下,我们都是将多个对象重叠在一 

起,提供自己期望的功能。我们之所以感到Java 的流库(Stream Library)异常复杂,正是由于为了创建单 

独一个结果流,却需要创建多个对象的缘故。  

很有必要按照功能对类进行分类。库的设计者首先决定与输入有关的所有类都从 InputStream 继承,而与输 

出有关的所有类都从OutputStream 继承。  



10。1。1 InputStream 的类型  



InputStream 的作用是标志那些从不同起源地产生输入的类。这些起源地包括(每个都有一个相关的 

InputStream子类):  

(1) 字节数组  

(2) String对象  

(3) 文件  

(4)  “管道”,它的工作原理与现实生活中的管道类似:将一些东西置入一端,它们在另一端出来。 (5) 一 

系列其他流,以便我们将其统一收集到单独一个流内。  

(6) 其他起源地,如 Internet 连接等(将在本书后面的部分讲述)。  

  

除此以外,FilterInputStream 也属于 InputStream 的一种类型,用它可为“破坏器”类提供一个基础类, 

以便将属性或者有用的接口同输入流连接到一起。这将在以后讨论。  

  

表 10。1 InputStream 的类型  

  

类 功能 构建器参数/如何使用  

  

ByteArrayInputStream 允许内存中的一个缓冲区作为 InputStream使用 从中提取字节的缓冲区/作为一个 

数据源使用。通过将其同一个 FilterInputStream 对象连接,可提供一个有用的接口  



                                                                       285 


…………………………………………………………Page 287……………………………………………………………

StringBufferInputStream 将一个 String 转换成 InputStream 一个String (字串)。基础的实施方案实际 

采用一个StringBuffer (字串缓冲)/作为一个数据源使用。通过将其同一个FilterInputStream 对象连 

接,可提供一个有用的接口  

FileInputStream 用于从文件读取信息 代表文件名的一个 String,或者一个 File 或FileDescriptor 对象 

/作为一个数据源使用。通过将其同一个FilterInputStream 对象连接,可提供一个有用的接口  

PipedInputString 产生为相关的 PipedOutputStream 写的数据。实现了“管道化”的概念  

PipedOutputStream /作为一个数据源使用。通过将其同一个FilterInputStream 对象连接,可提供一个有用 

的接口  

SequenceInputStream 将两个或更多的 InputStream 对象转换成单个 InputStream 使用 两个InputStream对 

象或者一个 Enumeration,用于 InputStream对象的一个容器/作为一个数据源使用。通过将其同一个 

FilterInputStream 对象连接,可提供一个有用的接口  

FilterInputStream 对作为破坏器接口使用的类进行抽象;那个破坏器为其他 InputStream类提供了有用的 

功能。参见表 10。3 参见表 10。3 /参见表10。3  



10。1。2 OutputStream 的类型  



这一类别包括的类决定了我们的输入往何处去:一个字节数组(但没有String;假定我们可用字节数组创建 

一个);一个文件;或者一个“管道”。  

除此以外,FilterOutputStream 为“破坏器”类提供了一个基础类,它将属性或者有用的接口同输出流连接 

起来。这将在以后讨论。  

  

表 10。2 OutputStream 的类型  

  

类 功能 构建器参数/如何使用  

  

ByteArrayOutputStream 在内存中创建一个缓冲区。我们发送给流的所有数据都会置入这个缓冲区。 可选缓 

冲区的初始大小/用于指出数据的目的地。若将其同FilterOutputStream 对象连接到一起,可提供一个有用 

的接口  

FileOutputStream 将信息发给一个文件 用一个 String 代表文件名,或选用一个File 或FileDescriptor 对 

象/用于指出数据的目的地。若将其同FilterOutputStream 对象连接到一起,可提供一个有用的接口  

PipedOutputStream 我们写给它的任何信息都会自动成为相关的PipedInputStream 的输出。实现了“管道 

化”的概念 PipedInputStream /为多线程处理指出自己数据的目的地/将其同FilterOutputStream 对象连 

接到一起,便可提供一个有用的接口  

FilterOutputStream 对作为破坏器接口使用的类进行抽象处理;那个破坏器为其他 OutputStream 类提供了 

有用的功能。参见表 10。4 参见表 10。4 /参见表10。4  



10。2 增添属性和有用的接口  



利用层次化对象动态和透明地添加单个对象的能力的做法叫作“装饰器”(Decorator)方案—— “方案”属 

于本书第 16 章的主题(注释①)。装饰器方案规定封装于初始化对象中的所有对象都拥有相同的接口,以便 

利用装饰器的“透明”性质——我们将相同的消息发给一个对象,无论它是否已被“装饰”。这正是在Java  

IO库里存在“过滤器”(Filter)类的原因:抽象的“过滤器”类是所有装饰器的基础类(装饰器必须拥有 

与它装饰的那个对象相同的接口,但装饰器亦可对接口作出扩展,这种情况见诸于几个特殊的“过滤器”类 

中)。  

子类处理要求大量子类对每种可能的组合提供支持时,便经常会用到装饰器——由于组合形式太多,造成子 

类处理变得不切实际。Java IO 库要求许多不同的特性组合方案,这正是装饰器方案显得特别有用的原因。 

但是,装饰器方案也有自己的一个缺点。在我们写一个程序的时候,装饰器为我们提供了大得多的灵活性 

 (因为可以方便地混合与匹配属性),但它们也使自己的代码变得更加复杂。原因在于Java IO 库操作不 

便,我们必须创建许多类—— “核心”IO类型加上所有装饰器——才能得到自己希望的单个 IO 对象。  

FilterInputStream 和FilterOutputStream (这两个名字不十分直观)提供了相应的装饰器接口,用于控制 

一个特定的输入流(InputStream)或者输出流(OutputStream)。它们分别是从 InputStream和 

OutputStream 衍生出来的。此外,它们都属于抽象类,在理论上为我们与一个流的不同通信手段都提供了一 



                                                                       286 


…………………………………………………………Page 288……………………………………………………………

个通用的接口。事实上,FilterInputStream 和 FilterOutputStream 只是简单地模仿了自己的基础类,它们 

是一个装饰器的基本要求。  



10。2。1 通过 FilterInputStream 从 InputStream 里读入数据  



FilterInputStream 类要完成两件全然不同的事情。其中,DataInputStream 允许我们读取不同的基本类型数 

据以及 String 对象(所有方法都以“read ”开头,比如readByte(),readFloat()等等)。伴随对应的 

DataOutputStream,我们可通过数据“流”将基本类型的数据从一个地方搬到另一个地方。这些“地方”是 

由表10。1 总结的那些类决定的。若读取块内的数据,并自己进行解析,就不需要用到DataInputStream。但 

在其他许多情况下,我们一般都想用它对自己读入的数据进行自动格式化。  

剩下的类用于修改 InputStream 的内部行为方式:是否进行缓冲,是否跟踪自己读入的数据行,以及是否能 

够推回一个字符等等。后两种�
返回目录 上一页 下一页 回到顶部 赞(11) 踩(15)
快捷操作: 按键盘上方向键 ← 或 → 可快速上下翻页 按键盘上的 Enter 键可回到本书目录页 按键盘上方向键 ↑ 可回到本页顶部!
温馨提示: 温看小说的同时发表评论,说出自己的看法和其它小伙伴们分享也不错哦!发表书评还可以获得积分和经验奖励,认真写原创书评 被采纳为精评可以获得大量金币、积分和经验奖励哦!