第十一章 异常处理
1. 异常处理
Java的异常处理(exception-handling)机制是个简捷、轻量化的执行期间例外状况处理方式,它让你能够将处理错误状况的程序代码摆在一个容易阅读的位置。我们可以通该方法的声明throw语句来得知该方法可能会出现的异常。
通常把有风险的程序代码放在try/catch块中,异常是一种exception类型的变量
try{
//危险动作
} catch(Exception ex){
//尝试恢复
}
方法可以抓住其他方法所抛出的异常。异常总是会丢回给调用方。会抛出异常的方法必须要声明它有可能会这么做。
// 有风险、会抛出异常的程序代码
public void takeRisk() throws BadException {
if (abandonAllHope) {
throw new BadException();
}
}
// 调用该方法的程序代码
public void crossFingers() {
try {
anObject.takeRisk();
} catch (BadException ex) {
System.out.println("Aaargh!");
ex.printStackTrace(); // 如果无法恢复,可以使用该方法列出有效信息
}
}
Exception类型
RuntimeException被称为不检查异常,你可以自己抛出与抓住它们,但是没有这个必要,编译器也不管,因为大部分的RuntimeException都是因为程序逻辑问题,而不是你所无法预测或防止的方法出现的执行期失败状况。try/catch 是用来处理真正的异常,而不是你程序的逻辑错误,该块要做的是恢复的尝试,或者至少会列出错误信息。
要点
-
方法可以再运行期间遇到问题时抛出异常
-
异常时Exception类型的对象
-
编译器不会注意RuntimeException类型的异常。RuntimeException不需要声明或被抱在try/catch的块中(然而你还是可以这么做)
-
编译器所关心的是称为检查异常的异常。程序必须认识有异常可能的存在
-
方法可以用throw关键词抛出异常对象:
throw new FikeIsTooSmallException( );
-
可能会抛出异常的方法必须声明成throw Exception
-
如果程序调用了有声明会抛出异常的方法,就得要告诉编译器已经注意到这件事
-
如果要处理异常状况,就把调用包在try/catch块中,并将异常处理/恢复程序放在catch块中
-
如果不打算处理异常,还是可以正式地将异常给ducking来通过编译,后面会解释ducking
finally
finally块是用来存放不管有没有异常都得执行的程序。
try {
turnOverOn();
x.bake();
} catch(BaikingException ex) {
ex.printStackTrace();
} finally {
turnOverOff(); //无论如何成功与否都会执行
}
即使try或者catch块中有return指令,finally还是会执行
,流程会跳到finally执行后再回到return指令。
2. 多态的异常
如果有必要的话,方法可以抛出多个异常,例如:
public class Laundry {
public void doLaundry() throw PantsException, LingerieException {
...
}
}
public class Foo {
public void go() {
Laundry laundry = new Laundry();
try {
laundry.doLaundry();
} catch(PantsException pex) {
... //恢复代码
} catch(LingerieException lex) {
... //恢复代码
}
}
}
因为异常时对象,所有异常也是有多态的,它也存在父类和子类。例如ClothingException就为PantsException、LingerieException和ShirtException的父类,而ShirtException又可以为TeeShirtException和DressShirtException的父类。
- 以异常的父类来声明会抛出的异常
public void doLaundry() throw ClothingException {
...
}
- 以所抛出的异常父类来catch异常
try {
laundry.doLaundry();
} catch(TeeShirtException tex) { //子类异常1
// solution
} catch(LingerieException lex) { //子类异常2
// solution
} catch(ClothingException cex) { //父类异常
// solution
}
顺序要注意,越下级的子类应放在最上面,父类放在下面
因为Java虚拟机只会从头开始往下找到第一个符合范围的异常处理块。如果父类放在上面,所有的异常都会在catch父类被执行。另外同级子类异常的先后顺序无所谓,因为它们的异常是不同的。
3. 声明(Duck)
如果不想处理异常的话,可以把它duck掉来避开。Duck其实就是把异常一次又一次丢给方法调用方,最后到main()中。
public class Washer {
Laundry laundry = new Laundry();
public void foo() throw ClothingException {
laundry.doLaundry();
}
public static void main (String[] args) throw ClothingException {
Washer a = new Washer();
a.foo();
}
}
两种满足编译器的有风险调用方式
- 处理
try {
laundry.doLaundry();
} catch(ClothingException ex) {
// solution
}
- 声明(duck)
public class Washer {
Laundry laundry = new Laundry();
public void foo() throw ClothingException { //把异常留给调用方foo()
laundry.doLaundry();
}
public static void main (String[] args) throw ClothingException {
//把异常扔给main(),这里也要声明,如果这里没有throw,将无法进行编译
Washer a = new Washer();
a.foo();
}
}
要么全部声明,要么处理异常
4. 异常处理规则
- catch与finally不能没有try
void go() {
Foo f = new Foo();
f.foof(); //缺少了try,无法编译
catch(FoofException ex) {
}
}
- try与catch之间不能有别的程序
try {
x.doStuff();
}
int y = 43; //这里不能放别的代码
catch(Exception ex){}
- try一定要有finally或者catch
try {
x.doStuff();
} finally {
...
} //合法程序
- 只带有finally的try必须要声明异常
void go() throw FooException {
try {
x.doStuff();
} finally {
...
}
} //没有catch的话必须进行声明