为什么需要异常
当程序运行时,如果遇到一些程序处理不了的问题,例如被除数等于0,尝试关闭不存在的流等等。在当前环境下,程序只能从当前环境跳出,把问题抛给上层的环境。
异常的流程
异常处理有两种模型:
- 终止模型:错误非常严重,程序无法回到产生问题点继续执行。所以一旦异常被抛出,就不能回来继续执行了
- 恢复模型:异常处理程序的工作是修正错误,然后再重新尝试调用出问题的方法,希望当异常处理后能继续执行程序。这种模型使用时就不能抛出异常,应该是调用方法来修正。
但是一般是使用终止模型,恢复模型在代码的便携和维护上相对困难和麻烦许多。
抛出异常时jvm做了什么
- 与创建java普通对象一样,使用new关键字在堆上创建对象
- 终止当前的执行路径,从当前环境中弹出堆异常对象的引用
- 异常处理机制接管程序
- 寻找一个适当的地方来继续执行程序("适当的地方"指异常处理程序)
- 异常处理程序将程序从错误状态下恢复执行,以使程序要么换一种方式运行(终止模型),要么继续运行下去(恢复模型)。
异常链
- 重新抛出异常的方法 fillInStackTrace() ,在这个方法之前的栈信息会丢失,当前地址会被认为是异常的发生地
- 构造函数的时候直接带上 上一个throwable对象,就会将异常信息继承下去,通过initCause()实现
RuntimeException如果不catch,则会一路向上,直到抛至main方法,这时候main方法会调用 e.printStackTrace(),将其错误报告输出给System.err
异常捕获
- 如果有多个catch(),一旦其中一个catch 子句结束,则处理程序的查找过程结束
- 异常捕获是就近原则,基类可以cover全部派生类,如果基类在派生类之前被捕获,编译器则会认为派生类的捕获永远不会执行,则编译不通过
自定义异常
派生类不能捕获基类抛出的异常
class A throw Exception1;
class B extend A throw Exception2;
try{
A a = new B();
a.tryThrowException();
}catch(Exception2 e){
// 此处是无法捕获,编译时就会报unhandle excetpion
}
- 派生类中的方法和基类方法无关,如果upcase,需要捕获/抛出基类的异常,如果new派生类本身,则需要捕获/抛出派生类的方法。
- 可以理解为派生类方法和基类方法无关,看定义的是哪个对象,以定义对象的异常处理为准。
- 异常说明本身并不属于方法类型的一部分,方法类型是由方法的名字与参数的类型组成的。
多异常情况要注意判断异常顺序和之后执行的关系
- 例如打开file,需要捕获/抛出FileNotFoundException。但是读取IO内容时,可能会遇到IOException
- 如果遇到IOException需要将IO流close(),但是如果是FileNotFoundException则不需要close()
- 在close()的时候也有可能会产生异常,需要做嵌套catch。
- 不要在构造函数的finally里close(),这样在函数构建完成后对象不可用
finally
由于java有GC,所以不需要在finally写析构函数
- finally的使用场景
- 恢复除了内存之外的资源至初始状态,如已经打开的tcp连接,文件io,公共参数等。
- 执行一些一定要执行的操作,不论是否发生异常:例如运行日志等。
- finally总是会执行,不论throw还是return
- 如果在需要关闭例如流,线程之类,最好使用try-finally块,finally块出现的exception就在外部catch
- Try-With-Resources
- Try-With-Resources会对在try子句的括号内的对象执行close()方法
- 使用前提:必须实现java.lang.AutoCloseable
- 如果构造函数出现异常,则jvm认为不确定是否能对这个对象进行操作,所以不会执行close()方法
try(
InputStream in = new FileInputStream(
new File("addr"));
){
in.read();
}catch(IOException e){
// 不需要执行 in.close();
}
总结
- 尽可能使用 try-with-resource。
- 在恰当的级别处理问题。(在知道该如何处理的情况下才捕获异常。)
- 解决问题并且重新调用产生异常的方法。
- 进行少许修补,然后绕过异常发生的地方继续执行。
- 用别的数据进行计算,以代替方法预计会返回的值。
- 把当前运行环境下能做的事情尽量做完,然后把相同的异常重抛到更高层。
- 把当前运行环境下能做的事情尽量做完,然后把不同的异常抛到更高层。
- 终止程序。
- 进行简化。(如果你的异常模式使问题变得太复杂,那用起来会非常痛苦也很烦人。)
- 让类库和程序更安全。(这既是在为调试做短期投资,也是在为程序的健壮性做长期投资。)
参考文献
部分内容引用至github内的开源翻译项目 LingCoder/OnJava8
本文只是我在拜读后的拙劣总结
传送门:https://github.com/LingCoder/OnJava8/blob/master/docs/book/15-Exceptions.md