《Java编程思想》第十二章 通过异常处理错误
前言:
本系列是我本人阅读java编程思想这本书的读书笔记,主要阅读第五章到第十七章以及第二十一章的内容,今天的笔记是第十二章
在java中有一个很重要的理念就是结构不佳的代码不能运行,简单来说就是代码必须保证一定的健壮性,当代码中有错误的时候,要能及时发现然后恢复,但是,指望所有的错误都能在编译期发现那是不可能的,这个时候,java的异常处理机制就起到了非常重要的作用。
1. 概念
‘’异常“这个词有“我对此感到意外“的意思。问题发生了,你或许不太清楚要怎么去解决这样的问题,但是你又知道不能只当没看见,这时候异常处理机制使得你可以把这个问题提交到更高一级的环境中,看看有没有别的地方可以处理解决这个问题,这样的动作叫做向上抛出异常,可以用方法签名的后面加上throws加上异常的名字,代表这个方法可能会产生一个类型为xxx的异常,还可以用throw new xxx的形式抛出一个异常对象。异常最重要的一点就是保证可以打断当前的执行路径,一旦发生了异常,那么程序就不可以按照正常的路径继续执行下去了。
2. 捕获异常
既然有抛出异常,自然就也要捕获异常,java提供了一个语句块,你可以在这个语句块里“尝试”各种可能出现异常的方法调用,当出现异常后,那就是try块,当异常发生后,需要捕获以及处理异常,这个时候就要使用catch语句块,catch语句块的参数列表中提供的是异常的类型,当异常被抛出后,异常处理机制就开始查找参数与异常类型匹配的异常处理程序,只要匹配上了,就进入catch语句块开始执行,一旦catch语句块结束,则查找过程结束,不再进行异常的处理。一个try语句块后可以跟随多个catch语句块,但是catch语句块有一个默认的机制,那就是捕获的异常类型的层次要从小到大,由具体到泛化,所有异常类型中最大的层次的类型是Exception,所以要记住当要捕获多个异常的时候,务必最后捕获Exception异常,因为如果把Exception异常放在第一个捕获的话无论如何都会执行到,而一旦catch执行完了就不会再进行后续的异常处理。
3. 自定义异常
java的异常体系难道能够预知所有在程序运行中可能发生的异常吗,显然是不可能的,不过还好,java允许我们自定义异常,要创建自定义异常,必须从已有的异常类继承,最好是选择意思相近的异常类继承,但是这样的异常类并不容易找,这样的话,可以继承Exception类来创建自定义异常,Exception类是所有异常类的公共父类。建立异常类的最简单的方法就是让编译器为你产生默认构造器,这样你就可以少写很多的代码。
4. java标准异常
Throwable这个类被用来表示任何可以作为异常被抛出的类,Throwable对象可以分为两种子类型,Error和Exception,Error用来表示编译时和系统错误,一般情况下程序员不用自己去解决(实际上你可能也解决不了),还有一种就是能够被抛出的Exception类型,程序猿一般关心的也就是Exception类型的异常。但是Exception类中又分了两种异常,一种是运行时异常也称不检查异常,就是说在程序运行的时候异常是不被检查的,一旦出现异常,程序会马上停止,一种是非运行时异常,也就是程序员必须手动的使用try catch语句捕获并处理的异常,一起来看一下异常类的结构层次
所有的自定义异常都是非运行时异常,都需要自己去捕获。
5. finally语句块
总有一些代码,是你想无论try块中的异常是否抛出都要去执行的,这个时候,finally语句块就派上了用场,finally在java中经常被用来关闭一些资源,通常这些资源和内存回收无关,比如网络流,IO流,数据库连接等等,即使try块中有异常被抛出了,这些资源也必须得到释放,不然就可能会引起一些资源的浪费乃至内存泄漏。
finally语句块有一个特点,那就是不论发生了什么,finally语句块中的代码总是会被执行,为了证明这一点,看一下这段代码
public class MultiplyReturns{
public static void f(int i){
try{
System.out.println("p1");
if(i==1) return;
System.out.println("p2");
if(i==2) return;
System.out.println("p3");
if(i==3) return;
System.out.println("end");
return;
}
finally{
System.out.println("yeah!!!");
}
}
public static void main(String[] args){
for(int i=1; i<=4; i++){
f(i);
}
}
}
按照我们的想法,return代表返回,那么方法就结束了,就不会再有语句被执行了,但实际上并不是这样,上一段代码的运行结果如下
p1
p1
p2
p1
p2
p3
end
yeah!!!
我们看到,最后的yeah!!!还是被打印了出来,说明了finally块中的语句无论如何都会被执行。
但是当try catch finally组合起来的时候,执行顺序就有讲究了,尤其是带上了return语句以后,网上有人尝试并整理了一下各种情况下的执行顺序,我把结论放在这里
情况1:try{} catch(){}finally{} return;
显然程序按顺序执行。
情况2:try{ return; }catch(){} finally{} return;
程序执行try块中return之前(包括return语句中的表达式运算)代码;
再执行finally块,最后执行try中return;
finally块之后的语句return,因为程序在try中已经return所以不再执行。
情况3:try{ } catch(){return;} finally{} return;
程序先执行try,如果遇到异常执行catch块,
有异常:则执行catch中return之前(包括return语句中的表达式运算)代码,再执行finally语句中全部代码,
最后执行catch块中return. finally之后也就是4处的代码不再执行。
无异常:执行完try再finally再return.
情况4:try{ return; }catch(){} finally{return;}
程序执行try块中return之前(包括return语句中的表达式运算)代码;
再执行finally块,因为finally块中有return所以提前退出。
情况5:try{} catch(){return;}finally{return;}
程序执行catch块中return之前(包括return语句中的表达式运算)代码;
再执行finally块,因为finally块中有return所以提前退出。
情况6:try{ return;}catch(){return;} finally{return;}
程序执行try块中return之前(包括return语句中的表达式运算)代码;
有异常:执行catch块中return之前(包括return语句中的表达式运算)代码;
则再执行finally块,因为finally块中有return所以提前退出。
无异常:则再执行finally块,因为finally块中有return所以提前退出。
最终结论:任何执行try 或者catch中的return语句之前,都会先执行finally语句,如果finally存在的话。
如果finally中有return语句,那么程序就return了,所以finally中的return是一定会被return的
6. 异常使用指南
书中罗列了一些如何使用异常的指南,我这里也简化并记录一下
- 在恰当的级别处理问题(在知道该如何处理的情况下才捕获异常)
- 解决问题并重新调用发生异常方法
- 进行一些修补,并绕过异常发生的地方继续执行
- 把当前情况下能做的事情做完,然后把异常向更高的层次跑出去
- 简化异常处理的代码
- 让你的程序更安全
总结
通过本章的学习,我们知道了java有着异常处理机制来帮助我们写出更安全更健壮的程序,了解了异常处理机制的一些功能和使用方法,看到了finally语句块中的语句在各种情况下的执行情况,最后还给出了异常使用的指南,到此,本章就结束了。
上一篇: Thking in java(第四版)-查缺补漏(第13章)
下一篇: overdraw优化