《Java编程思想》读书笔记(五)

第九章 违例差错控制

基本违例

  1. 产生一个违例时,发生的几件事情:

    • 按照与创建Java对象一样的方法创建违例对象:在内存“堆”中,使用new创建

    • 停止当前执行路径,从当前环境中释放违例对象的句柄

    • 违例控制器接管一切,并开始查找一个恰当的地方(违例控制器),用于继续程序的执行

  2. 违例控制器的职责是从问题中恢复,使程序要么尝试另一条执行路径,要么简单地继续

  3. 在所有标准违例中,存在两个构建器:默认构建器,需一个字符串作为自变量的构建器

  4. throw通过“掷”出一个违例,从原来的作用域中退出,但会先返回一个值,再退出方法或作用域;返回于一个恰当的“违例控制器”,距离违例“掷 ”出的地方可能相当遥远——在调用堆栈中要低上许多级

违例的捕获

  1. 如果在一个方法执行过程中发生违例,又不想直接throw离开方法,就设计一个特殊的代码块,用它来catch违例,即try块

  2. 生成的违例必须在某个地方终止,即违例控制器或违例控制模块,针对每种想捕获的违例类型,都必须有相应的违例控制器

  3. 控制器必须“紧接”try块后面,若“掷”出一个违例,违例控制机制会搜寻自变量与违例类型相符的第一个控制器,进入到那个catch从句中,只有相符的catch从句才会得到执行(与switch语句不同,switch在每个case后都需一个break命令,防止误执行其他语句)

  4. 中断:只要“掷”出一个违例,就表明没有办法补救错误,无法返回违例发生的地方

    恢复:违例得到控制后仍想继续执行,此时违例更想一个方法调用

  5. 通过捕获基础类违例类型Exception可捕获所有类型的违例,在实际使用中最好将其置于控制器表的末尾,防止跟随其后的任何特殊违例控制器失效

  6. 在catch块中重新“掷”出违例,会导致违例进入更高一级环境的违例控制器中。如果只是简单的重新掷出当前违例,其有关的信息会与违例起源地对应,而不是与重新掷出它的地点对应;如果调用fillInStackTrace()后再掷出,会将当前堆栈信息填充到原来的违例对象中,即违例对象的相关信息会与重新掷出的地点对应

  7. fillInStackTrace()会生成一个Throwable对象句柄(Throwable是Exception的一个基础类),所以Throwable类必须在违例规格中出现

  8. 从一个已捕获的违例中重新掷出一个不同的违例,与原违例起源地有关的信息会全部丢失,留下的是与新的throw有关的信息

标准Java违例

  1. Throwable包含两种常规类型:
    • Error:编译期和系统错误,一般不必特意捕获他们
    • Exception:从任何Java标准库的类方法中掷出的基本类型
  2. RuntimeException用于指出编程中的错误,几乎永远不必专门捕获,它在默认情况下会自动得到处理,可选择掷出一部分RuntimeException
  3. 假如一个RuntimeException获得到达main()的所有途径,同时不被捕获,那么当程序退出时,会为那个违例调用printStackTrace()

创建自己的违例

创建自己的违例,必须从一个现有的违例类型继承

违例的限制

原书示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
class Exception1 extends Exception {}
class Exception2 extends Exception1 {}
class Exception3 extends Exception1 {}
class Exception4 extends Exception {}
class Exception5 extends Exception4 {}
class Exception6 extends Exception2 {}

// 基础类
abstract class Inning {
Inning() throws Exception1 {}
void event () throws Exception1 {
// 实际不抛出任何异常
}
abstract void atBat() throws Exception2, Exception3;
void walk() {}
}

// 接口
interface Storm {
void event() throws Exception5;
void rainHard() throws Exception5;
}

// 继承基础类并实现接口
public class StormyInning extends Inning implements Storm {
// 可以为构造器添加新违例,但必须处理基础构造器的违例(这里Exception1是必须要处理的)
StormyInning() throws Exception1, Exception5 {}
StormyInning(String s) throws Exception1, Exception2 {}
// 常规方法必须符合基类
// 编译错误,因为基类没有违例
void walk() throws Exception6 {}
// 接口不能向基类中的现有方法添加异常
// 编译错误,基类中event()方法没有抛出Exception5违例(即使接口中有)
public void event() throws Exception5 {}
// 如果该方法基类中没有,那么可以添加新违例:
public void rainHard() throws Exception5 {}
// 即使基类中抛出违例,现有类中也可以不抛出:
public void event() {}
// 重写的方法会抛出继承的异常:
void atBat() throws Exception6 {}
public static void main(String[] args) {
try {
// 派生版本中不会触发Exception3
// 明确操作一个StormyInning对象,编译器会强迫我们只捕获特定于那个类的违例
StormyInning si = new StormyInning();
si.atBat();
} catch(Exception6 e) {
} catch(Exception5 e) {
} catch(Exception1 e) {}
try {
//上溯造型必须捕获方法的基类版本中的异常:
Inning i = new StormyInning();
i.atBat();
} catch(Exception3 e) {
} catch(Exception2 e) {
} catch(Exception5 e) {
} catch(Exception1 e) {}
}
}

用finally清除

完整的违例控制:

1
2
3
4
5
6
7
8
9
10
try { 
// 要保卫的区域:
// 可能“掷”出 A,B,或C 的危险情况
} catch (A a1) {
// 控制器 A
} catch (B b1) {
// 控制器 B
} finally {
// 每次都会发生的情况
}
  1. 无论是否掷出违例,finally从句都会执行
  2. 即使违例不在当前的catch从句集里捕获,finally都会在违例控制机制转到更高级别搜索一个控制器之前得以执行

违例匹配

  1. “掷”出一个违例后,违例控制系统会按当初编写的顺序搜索“最接近”的控制器,一旦找到相符的控制器,就认为违例已得到控制,不再进行更多的搜索工作

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

  3. 违例准则:

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

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

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

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

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

    (6)中止程序执行

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

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