简介

Java的异常处理依赖于你已经知道所调用的方法是有风险的(也就是方法可能产生异常),因此你可编写出处理此可能性的代码,如果你知道调用某个方法可能会有异常状况,你就可以与预先准备好对问题的处理程序,或者是从错误中恢复。

在Java中最好将有风险的程序代码包含在try/catch块中,这样才能对异常进行捕获和处理。

异常是一种Exception类型的对象。

格式:

1
2
3
4
5
try{
//危险动作
}catch(Exception ex){//因为异常是对象,所以你catch住的也是对象。
//尝试恢复
}

创建和抛出异常

没错,我们不仅能写处理异常的程序,也可以自己创建和抛出异常,具体方式请往下看。

创建有风险、会抛出异常的程序代码:

1
2
3
4
5
public void takeRisk() throws BadException {//必须声明它会抛出BadException
if(abandonAllHope){
throw new BadException();//创建Exception对象并抛出
}
}

调用该方法的程序代码

1
2
3
4
5
6
7
8
public void crossFingers(){
try{
anObject.takeRisk();
}catch(BadException ex){
System.out.printIn("Aaargh!");
ex.printStackTrace();//如果无法从异常中恢复,至少也使用printStackTrace()列出有用的信息
}
}

如果你有抛出异常,则一定要使用throw来声明这件事。

注意:

  • 编译器不会注意RuntimeException类型的异常。RuntimeExcetion不需要声明或被包含在try/catch的块中(然而你还是可以这样做)。编译器所关心的是成为检查异常(checked exception)的异常,程序必须要认识有以上可能的存在。
  • 方法可以用trow关键系抛出异常对象:
    • throw new FileIsTooSmallException();
  • 可能会抛出异常的方法必须声明成throws Exception

try/catch块的流程控制

当你调用有风险的方法时,发生的事有两种可能的情况:

  1. 成功的把try块完成
  2. 把异常丢回调用方的方法

finally:无论如何都要执行的部分

打个比方,你做菜,得把火打开,然后会有两种情况,一种是全程没问题,你顺利把菜炒完,另一种是出现状况了,比如锅漏了这种异常导致你做菜大业失败,但是不管哪种情况,发生之后都必须执行的是把炉子关掉,而关炉子这个流程就是放在finally块中的。

很明显,finally块是用来存放不管有没有异常都得执行的程序。

格式:

1
2
3
4
5
6
7
8
try{
turnOvenOn();
x.bake();
}catch(BakingException ex){
ex.printStackTrace();
}finally{
turnOvenOff();
}

值得注意的是,就酸try或catch块中有return子凌,finally还是会执行,流程调到finally然后在回到return指令,就是这么厉害。

举个栗子:

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
public class TestExceptions {

public static void main(String[] args) {
String test = "no";
//String test = "yes";
try{
System.out.println("start try");
doRisky(test);
System.out.println("end try");
} catch (ScaryException se) {
System.out.println("scary exception");
} finally {
System.out.println("finally");
}
System.out.println("end of main");
}

//(API中没有此类)因此要自己写ScaryException类
static void doRisky(String test) throws ScaryException {
System.out.println("start risky");
if("yes".equals(test)){
throw new ScaryException();
}
System.out.println("end risky");
return;
}
}

运行结果为:

start try
start risky
end risky
end try
finally
end of main

若第三行改为“String test = "no";”则结果应为:
start try
start risky
scary exception
finally
end of main

多重异常

如果有必要,方法可以排除多个异常。但该方法的声明必须要有含有全部可能的检查异常(若两个或两个以上的异常有共同的父类是,可以只声明该父类就行)

格式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class Laundry{
public void doLaundry() throws PantsException,LingerieException{
//有可能抛出两个异常的程序代码
}
}

public class Foo{
public void go(){
Laundry laundry=new Laundry();
try{
laundry.doLaundry();
}catch(PantsException pex){
//恢复程序代码
}catch(LingerieException pex){
//恢复程序代码
}
}
}

异常具有多态性

是基于类的继承关系得来的,父类或者子类的异常可以直接catch父类的异常来包含所有异常,catch子类的异常则只能用来处理子类的异常,对父类的不起作用。但是这也不意味着就可以都直接catch父类的异常来省去catch子类异常的步骤,因为直接catch父类的异常会导致不知道是哪个子类发生的异常,你会搞不清到底哪里出错了。

有多个catch块时要从大到小排列

“从大到小”指的是按照类的继承关系,从最下面的子类放第一个位置,最开始的父类放最下面。兄弟之间次序不重要,可以随便放。

duck异常

没错,不想处理的异常可以duck(躲避)掉。

方法时让调用的方法抛出异常,让调用那个方法的方法也抛出异常,就像踢皮球一样,不去管那个异常,只管抛出去,自己不管。既然都不管,这样踢来踢去最后只能落到Java虚拟机上,而Java虚拟机并不在意异常,所以程序就能编译通过。

1
2
3
4
5
6
7
8
9
10
11
12
public class Test {
Laundry laundry=new Laundry();

public void foo() throws ClothingException{
laundry.doLaundry();
}

public static void main(String[] args) throws ClothingException{
Washer a=new Washer();
a.foo();
}
}

以上代码如果将main函数里的throws ClothingException去掉,即没有duck掉异常,则无法通过编译,还会出现“unzepozted eception”的错误信息。如果不想duck掉,就老老实实用try/catch块将a.foo()这一危险操作包起来。

异常处理的结构规则

  • catch与finally不能没有try
  • try与catch之间不能有程序
  • try一定要有catch或finally
  • 只带有finally的try必须要声明异常

栗子:

1
2
3
4
5
void go() throws FooException{//只带有finally的try
try{
x.doStuff();
}finally{ }
}