友情提示:如果本网页打开太慢或显示不完整,请尝试鼠标右键“刷新”本网页!
Java编程思想第4版[中文版](PDF格式)-第67部分
快捷操作: 按键盘上方向键 ← 或 → 可快速上下翻页 按键盘上的 Enter 键可回到本书目录页 按键盘上方向键 ↑ 可回到本页顶部! 如果本书没有阅读完,想下次继续接着阅读,可使用上方 "收藏到我的浏览器" 功能 和 "加入书签" 功能!
若位于一个方法内部,并“掷”出一个违例(或在这个方法内部调用的另一个方法产生了违例),那个方法
就会在违例产生过程中退出。若不想一个 throw 离开方法,可在那个方法内部设置一个特殊的代码块,用它
捕获违例。这就叫作“try 块”,因为要在这个地方“尝试”各种方法调用。try 块属于一种普通的作用域,
用一个 try 关键字开头:
try {
// 可能产生违例的代码
}
若用一种不支持违例控制的编程语言全面检查错误,必须用设置和错误检测代码将每个方法都包围起来——
即便多次调用相同的方法。而在使用了违例控制技术后,可将所有东西都置入一个try 块内,在同一地点捕
获所有违例。这样便可极大简化我们的代码,并使其更易辨读,因为代码本身要达到的目标再也不会与繁复
的错误检查混淆。
264
…………………………………………………………Page 266……………………………………………………………
9。2。2 违例控制器
当然,生成的违例必须在某个地方中止。这个“地方”便是违例控制器或者违例控制模块。而且针对想捕获
的每种违例类型,都必须有一个相应的违例控制器。违例控制器紧接在try 块后面,且用 catch (捕获)关
键字标记。如下所示:
try {
// Code that might generate exceptions
} catch(Type1 id1) {
// Handle exceptions of Type1
} catch(Type2 id2) {
// Handle exceptions of Type2
} catch(Type3 id3) {
// Handle exceptions of Type3
}
// etc。。。
每个 catch 从句——即违例控制器——都类似一个小型方法,它需要采用一个(而且只有一个)特定类型的
自变量。可在控制器内部使用标识符(id1,id2等等),就象一个普通的方法自变量那样。我们有时也根本
不使用标识符,因为违例类型已提供了足够的信息,可有效处理违例。但即使不用,标识符也必须就位。
控制器必须“紧接”在 try 块后面。若“掷”出一个违例,违例控制机制就会搜寻自变量与违例类型相符的
第一个控制器。随后,它会进入那个catch 从句,并认为违例已得到控制(一旦catch 从句结束,对控制器
的搜索也会停止)。只有相符的catch 从句才会得到执行;它与switch 语句不同,后者在每个 case 后都需
要一个break 命令,防止误执行其他语句。
在 try 块内部,请注意大量不同的方法调用可能生成相同的违例,但只需要一个控制器。
1。 中断与恢复
在违例控制理论中,共存在两种基本方法。在“中断”方法中(Java 和 C++提供了对这种方法的支持),我
们假定错误非常关键,没有办法返回违例发生的地方。无论谁只要“掷”出一个违例,就表明没有办法补救
错误,而且也不希望再回来。
另一种方法叫作“恢复”。它意味着违例控制器有责任来纠正当前的状况,然后取得出错的方法,假定下一
次会成功执行。若使用恢复,意味着在违例得到控制以后仍然想继续执行。在这种情况下,我们的违例更象
一个方法调用——我们用它在 Java 中设置各种各样特殊的环境,产生类似于“恢复”的行为(换言之,此时
不是“掷”出一个违例,而是调用一个用于解决问题的方法)。另外,也可以将自己的try 块置入一个
while 循环里,用它不断进入 try 块,直到结果满意时为止。
从历史的角度看,若程序员使用的操作系统支持可恢复的违例控制,最终都会用到类似于中断的代码,并跳
过恢复进程。所以尽管“恢复”表面上十分不错,但在实际应用中却显得困难重重。其中决定性的原因可能
是:我们的控制模块必须随时留意是否产生了违例,以及是否包含了由产生位置专用的代码。这便使代码很
难编写和维护——大型系统尤其如此,因为违例可能在多个位置产生。
9。2。3 违例规范
在Java 中,对那些要调用方法的客户程序员,我们要通知他们可能从自己的方法里“掷”出违例。这是一种
有礼貌的做法,只有它才能使客户程序员准确地知道要编写什么代码来捕获所有潜在的违例。当然,若你同
时提供了源码,客户程序员甚至能全盘检查代码,找出相应的 throw 语句。但尽管如此,通常并不随同源码
提供库。为解决这个问题,Java 提供了一种特殊的语法格式(并强迫我们采用),以便礼貌地告诉客户程序
员该方法会“掷”出什么违例,令对方方便地加以控制。这便是我们在这里要讲述的“违例规范”,它属于
方法声明的一部分,位于自变量(参数)列表的后面。
违例规范采用了一个额外的关键字:throws;后面跟随全部潜在的违例类型。因此,我们的方法定义看起来
应象下面这个样子:
void f() throws tooBig; tooSmall; divZero { //。。。
若使用下述代码:
265
…………………………………………………………Page 267……………………………………………………………
void f() ' // 。。。
它意味着不会从方法里“掷”出违例(除类型为RuntimeException 的违例以外,它可能从任何地方掷出——
稍后还会详细讲述)。
但不能完全依赖违例规范——假若方法造成了一个违例,但没有对其进行控制,编译器会侦测到这个情况,
并告诉我们必须控制违例,或者指出应该从方法里“掷”出一个违例规范。通过坚持从顶部到底部排列违例
规范,Java 可在编译期保证违例的正确性(注释②)。
②:这是在 C++违例控制基础上一个显著的进步,后者除非到运行期,否则不会捕获不符合违例规范的错
误。这使得 C++的违例控制机制显得用处不大。
我们在这个地方可采取欺骗手段:要求“掷”出一个并没有发生的违例。编译器能理解我们的要求,并强迫
使用这个方法的用户当作真的产生了那个违例处理。在实际应用中,可将其作为那个违例的一个“占位符”
使用。这样一来,以后可以方便地产生实际的违例,毋需修改现有的代码。
9。2。4 捕获所有违例
我们可创建一个控制器,令其捕获所有类型的违例。具体的做法是捕获基础类违例类型Exception (也存在
其他类型的基础违例,但Exception 是适用于几乎所有编程活动的基础)。如下所示:
catch(Exception e) {
System。out。println(〃caught an exception〃);
}
这段代码能捕获任何违例,所以在实际使用时最好将其置于控制器列表的末尾,防止跟随在后面的任何特殊
违例控制器失效。
对于程序员常用的所有违例类来说,由于 Exception 类是它们的基础,所以我们不会获得关于违例太多的信
息,但可调用来自它的基础类Throwable 的方法:
String getMessage()
获得详细的消息。
String toString()
返回对Throwable 的一段简要说明,其中包括详细的消息(如果有的话)。
void printStackTrace()
void printStackTrace(PrintStream)
打印出Throwable 和 Throwable 的调用堆栈路径。调用堆栈显示出将我们带到违例发生地点的方法调用的顺
序。
第一个版本会打印出标准错误,第二个则打印出我们的选择流程。若在Windows 下工作,就不能重定向标准
错误。因此,我们一般愿意使用第二个版本,并将结果送给System。out;这样一来,输出就可重定向到我们
希望的任何路径。
除此以外,我们还可从 Throwable 的基础类Object (所有对象的基础类型)获得另外一些方法。对于违例控
制来说,其中一个可能有用的是getClass(),它的作用是返回一个对象,用它代表这个对象的类。我们可依
次用getName()或toString()查询这个Class 类的名字。亦可对 Class 对象进行一些复杂的操作,尽管那些
操作在违例控制中是不必要的。本章稍后还会详细讲述 Class 对象。
下面是一个特殊的例子,它展示了 Exception 方法的使用(若执行该程序遇到困难,请参考第3 章3。1。2 小
节“赋值”):
//: ExceptionMethods。java
// Demonstrating the Exception Methods
package c09;
public class ExceptionMethods {
public static void main(String'' args) {
try {
266
…………………………………………………………Page 268……………………………………………………………
throw new Exception(〃Here's my Exception〃);
} catch(Exception e) {
System。out。println(〃Caught Exception〃);
System。out。println(
〃e。getMessage(): 〃 + e。getMessage());
System。out。println(
〃e。toString(): 〃 + e。toString());
System。out。println(〃e。printStackTrace():〃);
e。printStackTrace();
}
}
} ///:~
该程序输出如下:
Caught Exception
e。getMessage(): Here's my Exception
e。toString(): java。lang。Exception: Here's my Exception
e。printStackTrace():
java。lang。Exception: Here's my Exception
at ExceptionMethods。main
可以看到,该方法连续提供了大量信息——每类信息都是前一类信息的一个子集。
9。2 。5 重新“掷”出违例
在某些情况下,我们想重新掷出刚才产生过的违例,特别是在用Exception 捕获所有可能的违例时。由于我
们已拥有当前违例的句柄,所以只需简单地重新掷出那个句柄即可。下面是一个例子:
catch(Exception e) {
System。out。println(〃一个违例已经产生〃);
throw e;
}
重新“掷”出一个违例导致违例进入更高一级环境的违例控制器中。用于同一个 try 块的任何更进一步的
catch 从句仍然会被忽略。此外,与违例对象有关的所有东西都会得到保留,所以用于捕获特定违例类型的
更高一级的控制器可以从那个对象里提取出所有信息。
若只是简单地重新掷出当前违例,我们打印出来的、与printStackTrace()内的那个违例有关的信息会与违
例的起源地对应,而不是与重新掷出它的地点对应。若想安装新的堆栈跟踪信息,可调用
fillInStackTrace(),它会返回一个特殊的违例对象。这个违例的创建过程如下:将当前堆栈的信息填充到
原来的违例对象里。下面列出它的形式:
//: Rethrowing。java
// Demonstrating fillInStackTrace()
public class Rethrowing {
public static void f() throws Exception {
System。out。println(
〃originating the exception in f()〃);
throw new Exception(〃thrown from f()〃);
}
public static void g() throws Throwable {
try {
f();
} catch(Exception e) {
267
…………………………………………………………Page 269……………………………………………………………
System。out。println(
〃Inside g(); e。printStackTrace()〃);
e。printStackTrace();
throw e; // 17
// throw e。fillInStackTrace(); // 18
}
}
public static void
main(String'' args) throws Throwable {
try {
g();
} catch(Exception e) {
System。out。println(
〃Caught in main; e。printStackTrace()〃);
e。printStackTrace();
}
}
} ///:~
其中最重要的行号在注释内标记出来。注意第 17行没有设为注释行。它的输出结果如下:
originating the exception in f()
Inside g(); e。printStackTrace()
java。lang。Exception: thrown from f()
at Rethrowing。f(Rethrowing。java:8)
at Rethrowing。g(Rethrowing。java:12)
at Rethrowing。main(Rethrowing。java:24)
Caught in main; e。printStackTrace()
java。lang。Exception: thrown from f()
at Rethrowing。f(Rethrowing。java:8)
at Rethrowing。g(Rethrowing。java:12)
at Rethrowing。main(Rethrowing。java:24)
因此,违例堆栈路径无论如何都会记住它的真正起点,无论自己被重复“掷”了好几次。
若将第 17 行标注(变成注释行),而撤消对第 18 行的标注,就会换用fillInStackTrace(),结果如下:
originating the exception in f()
Inside g(); e。printStackTrace()
java。lang。Exception: thrown from f()
at Rethrowing。f(Rethrowing。java:8)
at Rethrowing。g(Rethrowing。java:12)
at Rethrowing。main(Rethrowing。java:24)
Caught in main; e。printStackTrace()
java。lang。Exception: thrown from f()
at Rethrowing。g(Rethrowing。java:18)
at Rethrowing。main(Rethrowing。java:24)
由于使用的是fillInStackTrace() ,第18 行成为违例的新起点。
针对 g()和 main(),Throwable 类必须在违例规格中出现,因为fillInStackTrace()会生成一个 Throwable
对象的句柄。由于 Throwable 是 Exception 的一个基础类,所以有可能获得一个能够“掷”出的对象(具有
Throwable 属性),但却并非一个 Exception (违例)。因此,在main()中用于Exception 的句柄可能丢失
自己的目标。为保证所有东西均井然有序,编译器强制Throwable 使用一个违例规范。举个例子来说,下述
快捷操作: 按键盘上方向键 ← 或 → 可快速上下翻页 按键盘上的 Enter 键可回到本书目录页 按键盘上方向键 ↑ 可回到本页顶部!
温馨提示: 温看小说的同时发表评论,说出自己的看法和其它小伙伴们分享也不错哦!发表书评还可以获得积分和经验奖励,认真写原创书评 被采纳为精评可以获得大量金币、积分和经验奖励哦!