异常简介

异常是为了在出错的时候,提供一个机制把控制权交给能够处理这个错误的代码手上。如果没有异常,异常产生处和处理处的代码可能很远。

异常的层次结构:

基类:Throwable 直接继承Throwable:Error和Exception Error被设计成虚拟机内部错误,程序员不应该抛出 Exception是我们平常关注的范围。

IOException和RuntimeException是Exception下的两个常见的直接子类。Exception还有更多直接子类。

所有派生于Error类或RuntimeException类的异常都是非检查型unchecked异常。 所有其他异常都是检查型checked异常。

语义上,RuntimeException指的是编程错误导致的异常,如

  • ArrayIndexOutOfBoundsException
  • NullPointerException
  • ClassCastException

创建异常类

我们需要继承一个Exception的子类。同时惯例我们应该实现两个构造器:一个无参和一个带详细信息字符串的构造器。

异常的捕获和传播

要捕获异常,需要建立一个try-catch块。 如果在try块中遇到catch指定的异常,那么直接跳到对应catch语句块。 预估遇到catch未指定的异常,那么方法直接退出。 如果try块正常运行,那么不运行catch块,继续运行方法的后续代码。

如何判断处理还是传递异常? 在工程实践中,推荐遵循**尽早抛出,延迟捕获(Throw early, catch late)**的原则: 在代码最底层的逻辑验证阶段,一旦检测到异常状态立即抛出;在整个调用链路中,除非能够真正处理问题或必须转译异常,否则应一路放行,交由拥有全局业务视野的顶层框架或统一异常处理器(如 Spring 的 @ExceptionHandler)进行集中处理。

finnally和try-with-resources

finally子句:无论是否排除是否被捕获的异常,都会在最后执行。 可以只有finally没有catch

不要在finally中使用throw, return, break, continue等改变控制流的语句。 一个良好的实践是使用两个独立的try,try-finally用于关闭资源,try-catch用于处理异常。

java7以上支持try-with-resources

对于实现了AutoClosable接口:

interface AutoClosable {
  void close() throws Exception
}

的类(资源),try-with-resources语法会在try块结束后自动关闭这些资源。

try (Resource res = ...) {
  do with res.....
} // 不管有没有异常,是否被捕获,都会在这里调用res.close()
catch (Exception e) {
  //...
}

java9之后,可以提供effectively final的变量了:

// out 是方法的参数,满足effectively final
try (out) {
  // do sth
} // out.close

没什么人在意的细节:在try-with-resources中,如果try块和close都抛出了异常,close抛出的异常会被抑制。它们被用addSuppressed方法加到try抛的异常上,然后重新抛出try抛出的异常。如果你感兴趣,可以使用getSuppressed方法获取这些异常。