java学习与总结-Exception

为什么需要异常

当程序运行时,如果遇到一些程序处理不了的问题,例如被除数等于0,尝试关闭不存在的流等等。在当前环境下,程序只能从当前环境跳出,把问题抛给上层的环境。

异常的流程

异常处理有两种模型:

  • 终止模型:错误非常严重,程序无法回到产生问题点继续执行。所以一旦异常被抛出,就不能回来继续执行了
  • 恢复模型:异常处理程序的工作是修正错误,然后再重新尝试调用出问题的方法,希望当异常处理后能继续执行程序。这种模型使用时就不能抛出异常,应该是调用方法来修正。

但是一般是使用终止模型,恢复模型在代码的便携和维护上相对困难和麻烦许多。

抛出异常时jvm做了什么

  1. 与创建java普通对象一样,使用new关键字在堆上创建对象
  2. 终止当前的执行路径,从当前环境中弹出堆异常对象的引用
  3. 异常处理机制接管程序
  4. 寻找一个适当的地方来继续执行程序("适当的地方"指异常处理程序)
  5. 异常处理程序将程序从错误状态下恢复执行,以使程序要么换一种方式运行(终止模型),要么继续运行下去(恢复模型)。

异常链

  • 重新抛出异常的方法 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
}
  1. 派生类中的方法和基类方法无关,如果upcase,需要捕获/抛出基类的异常,如果new派生类本身,则需要捕获/抛出派生类的方法。
  2. 可以理解为派生类方法和基类方法无关,看定义的是哪个对象,以定义对象的异常处理为准。
  3. 异常说明本身并不属于方法类型的一部分,方法类型是由方法的名字与参数的类型组成的。

多异常情况要注意判断异常顺序和之后执行的关系

  1. 例如打开file,需要捕获/抛出FileNotFoundException。但是读取IO内容时,可能会遇到IOException
  2. 如果遇到IOException需要将IO流close(),但是如果是FileNotFoundException则不需要close()
  3. 在close()的时候也有可能会产生异常,需要做嵌套catch。
  4. 不要在构造函数的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();
 }

总结

  1. 尽可能使用 try-with-resource。
  2. 在恰当的级别处理问题。(在知道该如何处理的情况下才捕获异常。)
  3. 解决问题并且重新调用产生异常的方法。
  4. 进行少许修补,然后绕过异常发生的地方继续执行。
  5. 用别的数据进行计算,以代替方法预计会返回的值。
  6. 把当前运行环境下能做的事情尽量做完,然后把相同的异常重抛到更高层。
  7. 把当前运行环境下能做的事情尽量做完,然后把不同的异常抛到更高层。
  8. 终止程序。
  9. 进行简化。(如果你的异常模式使问题变得太复杂,那用起来会非常痛苦也很烦人。)
  10. 让类库和程序更安全。(这既是在为调试做短期投资,也是在为程序的健壮性做长期投资。)

参考文献

部分内容引用至github内的开源翻译项目 LingCoder/OnJava8
本文只是我在拜读后的拙劣总结
传送门:https://github.com/LingCoder/OnJava8/blob/master/docs/book/15-Exceptions.md

你可能感兴趣的:(java学习与总结-Exception)