📄 chapter9.htm
字号:
<br>
若调用了break和continue语句,finally语句也会得以执行。请注意,与作上标签的break和continue一道,finally排除了Java对goto跳转语句的需求。<br>
<br>
9.6.2 缺点:丢失的违例<br>
一般情况下,Java的违例实施方案都显得十分出色。不幸的是,它依然存在一个缺点。尽管违例指出程序里存在一个危机,而且绝不应忽略,但一个违例仍有可能简单地“丢失”。在采用finally从句的一种特殊配置下,便有可能发生这种情况:<br>
<br>
430页上程序<br>
<br>
输出如下:<br>
<br>
430页下程序<br>
<br>
可以看到,这里不存在VeryImportantException(非常重要的违例)的迹象,它只是简单地被finally从句中的HoHumException代替了。<br>
这是一项相当严重的缺陷,因为它意味着一个违例可能完全丢失。而且就象前例演示的那样,这种丢失显得非常“自然”,很难被人查出蛛丝马迹。而与此相反,C++里如果第二个违例在第一个违例得到控制前产生,就会被当作一个严重的编程错误处理。或许Java以后的版本会纠正这个问题(上述结果是用Java
1.1生成的)。<br>
<br>
9.7 构建器<br>
为违例编写代码时,我们经常要解决的一个问题是:“一旦产生违例,会正确地进行清除吗?”大多数时候都会非常安全,但在构建器中却是一个大问题。构建器将对象置于一个安全的起始状态,但它可能执行一些操作——如打开一个文件。除非用户完成对象的使用,并调用一个特殊的清除方法,否则那些操作不会得到正确的清除。若从一个构建器内部“掷”出一个违例,这些清除行为也可能不会正确地发生。所有这些都意味着在编写构建器时,我们必须特别加以留意。<br>
由于前面刚学了finally,所以大家可能认为它是一种合适的方案。但事情并没有这么简单,因为finally每次都会执行清除代码——即使我们在清除方法运行之前不想执行清除代码。因此,假如真的用finally进行清除,必须在构建器正常结束时设置某种形式的标志。而且只要设置了标志,就不要执行finally块内的任何东西。由于这种做法并不完美(需要将一个地方的代码同另一个地方的结合起来),所以除非特别需要,否则一般不要尝试在finally中进行这种形式的清除。<br>
在下面这个例子里,我们创建了一个名为InputFile的类。它的作用是打开一个文件,然后每次读取它的一行内容(转换为一个字串)。它利用了由Java标准IO库提供的FileReader以及BufferedReader类(将于第10章讨论)。这两个类都非常简单,大家现在可以毫无困难地掌握它们的基本用法:<br>
<br>
431-433页程序<br>
<br>
该例使用了Java 1.1 IO类。<br>
用于InputFile的构建器采用了一个String(字串)参数,它代表我们想打开的那个文件的名字。在一个try块内部,它用该文件名创建了一个FileReader。对FileReader来说,除非转移并用它创建一个能够实际与之“交谈”的BufferedReader,否则便没什么用处。注意InputFile的一个好处就是它同时合并了这两种行动。<br>
若FileReader构建器不成功,就会产生一个FileNotFoundException(文件未找到违例)。必须单独捕获这个违例——这属于我们不想关闭文件的一种特殊情况,因为文件尚未成功打开。其他任何捕获从句(catch)都必须关闭文件,因为文件已在进入那些捕获从句时打开(当然,如果多个方法都能产生一个FileNotFoundException违例,就需要稍微用一些技巧。此时,我们可将不同的情况分隔到数个try块内)。close()方法会掷出一个尝试过的违例。即使它在另一个catch从句的代码块内,该违例也会得以捕获——对Java编译器来说,那个catch从句不过是另一对花括号而已。执行完本地操作后,违例会被重新“掷”出。这样做是必要的,因为这个构建器的执行已经失败,我们不希望调用方法来假设对象已正确创建以及有效。<br>
在这个例子中,没有采用前述的标志技术,finally从句显然不是关闭文件的正确地方,因为这可能在每次构建器结束的时候关闭它。由于我们希望文件在InputFile对象处于活动状态时一直保持打开状态,所以这样做并不恰当。<br>
getLine()方法会返回一个字串,其中包含了文件中下一行的内容。它调用了readLine(),后者可能产生一个违例,但那个违例会被捕获,使getLine()不会再产生任何违例。对违例来说,一项特别的设计问题是决定在这一级完全控制一个违例,还是进行部分控制,并传递相同(或不同)的违例,或者只是简单地传递它。在适当的时候,简单地传递可极大简化我们的编码工作。getLine()方法会变成:<br>
String getLine() throws IOException {<br>
return in.readLine();<br>
}<br>
但是当然,调用者现在需要对可能产生的任何IOException进行控制。<br>
用户使用完毕InputFile对象后,必须调用cleanup()方法,以便释放由BufferedReader以及/或者FileReader占用的系统资源(如文件句柄)——注释⑥。除非InputFile对象使用完毕,而且到了需要弃之不用的时候,否则不应进行清除。大家可能想把这样的机制置入一个finalize()方法内,但正如第4章指出的那样,并非总能保证finalize()获得正确的调用(即便确定它会调用,也不知道何时开始)。这属于Java的一项缺陷——除内存清除之外的所有清除都不会自动进行,所以必须知会客户程序员,告诉他们有责任用finalize()保证清除工作的正确进行。<br>
<br>
⑥:在C++里,“破坏器”可帮我们控制这一局面。<br>
<br>
在Cleanup.java中,我们创建了一个InputFile,用它打开用于创建程序的相同的源文件。同时一次读取该文件的一行内容,而且添加相应的行号。所有违例都会在main()中被捕获——尽管我们可选择更大的可靠性。<br>
这个示例也向大家展示了为何在本书的这个地方引入违例的概念。违例与Java的编程具有很高的集成度,这主要是由于编译器会强制它们。只有知道了如何操作那些违例,才可更进一步地掌握编译器的知识。<br>
<br>
9.8 违例匹配<br>
“掷”出一个违例后,违例控制系统会按当初编写的顺序搜索“最接近”的控制器。一旦找到相符的控制器,就认为违例已得到控制,不再进行更多的搜索工作。<br>
在违例和它的控制器之间,并不需要非常精确的匹配。一个衍生类对象可与基础类的一个控制器相配,如下例所示:<br>
<br>
435页上程序<br>
<br>
Sneeze违例会被相符的第一个catch从句捕获。当然,这只是第一个。然而,假如我们删除第一个catch从句:<br>
<br>
435页下程序<br>
<br>
那么剩下的catch从句依然能够工作,因为它捕获的是Sneeze的基础类。换言之,catch(Annoyance
e)能捕获一个Annoyance以及从它衍生的任何类。这一点非常重要,因为一旦我们决定为一个方法添加更多的违例,而且它们都是从相同的基础类继承的,那么客户程序员的代码就不需要更改。至少能够假定它们捕获的是基础类。<br>
若将基础类捕获从句置于第一位,试图“屏蔽”衍生类违例,就象下面这样:<br>
<br>
436页上程序<br>
<br>
则编译器会产生一条出错消息,因为它发现永远不可能抵达Sneeze捕获从句。<br>
<br>
9.8.1 违例准则<br>
用违例做下面这些事情:<br>
(1) 解决问题并再次调用造成违例的方法。<br>
(2) 平息事态的发展,并在不重新尝试方法的前提下继续。<br>
(3) 计算另一些结果,而不是希望方法产生的结果。<br>
(4)
在当前环境中尽可能解决问题,以及将相同的违例重新“掷”出一个更高级的环境。<br>
(5)
在当前环境中尽可能解决问题,以及将不同的违例重新“掷”出一个更高级的环境。<br>
(6) 中止程序执行。<br>
(7)
简化编码。若违例方案使事情变得更加复杂,那就会令人非常烦恼,不如不用。<br>
(8)
使自己的库和程序变得更加安全。这既是一种“短期投资”(便于调试),也是一种“长期投资”(改善应用程序的健壮性)<br>
<br>
9.9 总结<br>
通过先进的错误纠正与恢复机制,我们可以有效地增强代码的健壮程度。对我们编写的每个程序来说,错误恢复都属于一个基本的考虑目标。它在Java中显得尤为重要,因为该语言的一个目标就是创建不同的程序组件,以便其他用户(客户程序员)使用。为构建一套健壮的系统,每个组件都必须非常健壮。<br>
在Java里,违例控制的目的是使用尽可能精简的代码创建大型、可靠的应用程序,同时排除程序里那些不能控制的错误。<br>
违例的概念很难掌握。但只有很好地运用它,才可使自己的项目立即获得显著的收益。Java强迫遵守违例所有方面的问题,所以无论库设计者还是客户程序员,都能够连续一致地使用它。<br>
<br>
9.10 练习<br>
(1) 用main()创建一个类,令其掷出try块内的Exception类的一个对象。为Exception的构建器赋予一个字串参数。在catch从句内捕获违例,并打印出字串参数。添加一个finally从句,并打印一条消息,证明自己真正到达那里。<br>
(2) 用extends关键字创建自己的违例类。为这个类写一个构建器,令其采用String参数,并随同String句柄把它保存到对象内。写一个方法,令其打印出保存下来的String。创建一个try-catch从句,练习实际操作新违例。<br>
(3) 写一个类,并令一个方法掷出在练习2中创建的类型的一个违例。试着在没有违例规范的前提下编译它,观察编译器会报告什么。接着添加适当的违例规范。在一个try-catch从句中尝试自己的类以及它的违例。<br>
(4) 在第5章,找到调用了Assert.java的两个程序,并修改它们,令其掷出自己的违例类型,而不是打印到System.err。该违例应是扩展了RuntimeException的一个内部类。</p>
<!--msthemeseparator--><p align="center"><img src="../_themes/inmotion/inmhorsa.gif" tppabs="http://member.netease.com/%7etransbot/Thinking%20in%20Java/_themes/inmotion/inmhorsa.gif" width="300" height="10"></p>
<p align="center"><a href="../../../../tppmsgs/msgs0.htm#1" tppabs="http://www.bruceeckel.com/">英文版主页</a> | <a href="../index.htm" tppabs="http://member.netease.com/%7etransbot/Thinking%20in%20Java/index.htm">中文版主页</a> | <a href="../contents/index.htm" tppabs="http://member.netease.com/%7etransbot/Thinking%20in%20Java/contents/index.htm">详细目录</a>
| <a href="../about/index.htm" tppabs="http://member.netease.com/%7etransbot/Thinking%20in%20Java/about/index.htm">关于译者</a></p>
</body>
</html>
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -