文章目录
  1. 1. Java的异常
    1. 1.1. 异常分类
  2. 2. 处理异常机制
    1. 2.1. 捕捉异常
    2. 2.2. 抛出异常
    3. 2.3. return语句的执行顺序
  3. 3. 异常的转译与异常链
    1. 3.1. 异常转译
    2. 3.2. 异常链
  4. 4. 最佳实践
  5. 5. 参考

java中的Exception是面试中经常被问到的模块,虽然每次都能或多或少的讲出一些内容,但是直到最近才开始在编程实践中应用起来。良好的使用Exception能让程序bug更少,更加健壮。

Java的异常

Throwable:有两个重要的子类:Exception(异常)和 Error(错误),二者都是 Java 异常处理的重要子类,各自都包含大量子类。

Error: 是程序无法处理的错误,表示运行应用程序中较严重问题。大多数错误与代码编写者执行的操作无关,而表示代码运行时 JVM(Java 虚拟机)出现的问题。这些异常发生时,Java虚拟机(JVM)一般会选择线程终止。

Exception: 是程序本身可以处理的异常。这种异常分两大类运行时异常和非运行时异常,程序中应当尽可能去处理这些异常。

异常分类

可查的异常(checked exceptions): 除了RuntimeException及其子类以外,其他的Exception类及其子类都属于可查异常。这种异常的特点是Java编译器会检查它,也就是说,当程序中可能出现这类异常,要么用try-catch语句捕获它,要么用throws子句声明抛出它,否则编译不会通过。

不可查的异常(unchecked exceptions): 包括运行时异常(RuntimeException与其子类)和错误(Error)

由上可知, Exception 这种异常分两大类运行时异常和非运行时异常(编译异常)。程序中应当尽可能去处理这些异常。

运行时异常:这些异常是不检查异常,程序中可以选择捕获处理,也可以不处理。这些异常一般是由程序逻辑错误引起的,程序应该从逻辑角度尽可能避免这类异常的发生。运行时异常的特点是Java编译器不会检查它,也就是说,当程序中可能出现这类异常,即使没有用try-catch语句捕获它,也没有用throws子句声明抛出它,也会编译通过。

非运行时异常 (编译异常):是RuntimeException以外的异常,类型上都属于Exception类及其子类。从程序语法角度讲是必须进行处理的异常,如果不处理,程序就不能编译通过。如IOException、SQLException等以及用户自定义的Exception异常,一般情况下不自定义检查异常。

处理异常机制

异常处理机制为:抛出异常,捕捉异常。

捕捉异常

try{
  //(尝试运行的)程序代码
}catch(异常类型 异常的变量名){
  //异常处理代码
}finally{
  //异常发生,方法返回之前,总是要执行的代码
}

抛出异常

public static void test3() throws Exception {
    //抛出一个检查异常
    throw new Exception("方法test3中的Exception");
} 

throw关键字是用于方法体内部,用来抛出一个Throwable类型的异常。如果抛出了检查异常,则还应该在方法头部声明方法可能抛出的异常类型。该方法的调用者也必须检查处理抛出的异常。如果所有方法都层层上抛获取的异常,最终JVM会进行处理,处理也很简单,就是打印异常消息和堆栈信息。如果抛出的是Error或RuntimeException,则该方法的调用者可选择处理该异常。

throws关键字用于方法体外部的方法声明部分,用来声明方法可能会抛出某些异常。仅当抛出了检查异常,该方法的调用者才必须处理或者重新抛出该异常。当方法的调用者无力处理该异常的时候,应该继续抛出,而不是囫囵吞枣一般在catch块中打印一下堆栈信息做个勉强处理。

return语句的执行顺序

  1. 如果finally{}块中有return语句,只执行finally{}块中的return语句
  2. 如果finally{}块中没有return语句,如果try{}有异常,则返回catch{}中的return语句,不然执行try{}中return语句,在这种情况下,return语句块是在finally{}之后执行

return并不是让函数马上返回,而是return语句执行后,将返回结果放置进函数栈中,此时函数并不是马上返回,它要执行finally语句后才真正开始返回。

异常的转译与异常链

异常转译

谓的异常转译就是将一种异常转换另一种新的异常,也许这种新的异常更能准确表达程序发生异常。

几乎所有带异常原因的异常构造方法都使用Throwable类型做参数,这也就为异常的转译提供了直接的支持,因为任何形式的异常和错误都Throwable的子类。

异常链

异常链顾名思义就是将异常发生的原因一个传一个串起来,即把底层的异常信息传给上层,这样逐层抛出。

try {         
    doLowerLevelThings();
} catch (LowerLevelException cause) {
     throw new HigherLevelException(cause);
}

异常链的实际应用很少,发生异常时候逐层上抛不是个好注意,上层拿到这些异常又能奈之何?而且异常逐层上抛会消耗大量资源,因为要保存一个完整的异常链信息.

最佳实践

  1. 提早抛出: 越早处理越早隔离异常。
  2. 具体明确: 不能通过简单的catch住exception来处理所有异常,而应该捕获具体的异常。
  3. 延迟捕获: 不要压制、隐瞒异常。将不能处理的异常往外抛,而不是捕获之后随便处理。捕获之后该拿异常怎么办?最不该做的就是什么都不做。
  4. 避免在方法中抛出(throw)或者捕获(catch)运行时异常RuntimeException和Error。
  5. 无论服务开发还是服务使用,都要尝试或者想到封装异常,提供友好错误设计的方案,最简单的是自定义一个业务Exception来封装.

我们对异常的一个基本期望是异常究竟该被谁捕获,如果被你的服务下游捕获,那么这必须是一个受检的异常,如果是系统自身需要,那么这个我个人认为是分阶段设计的,在初期,也就是未发布状态,这些Exception应该总是被抛出的,因为这样可以快速的让测试和质量控制人员发现系统崩溃的点。在发布阶段,异常可能需要被内部消化,这时受检异常就要提供给业务系统,让业务开发自行捕获异常。当然,好的系统架构可能会把Exception作为一个内部可见外部不可见的内容,而基于此完全封装一套error code对外,这应该算是比较友好的做法了,也是很多API设计时的标准规范。毕竟对外部透明,不要让用户看到你的Exception,这是非常友好的做法。

参考

  1. 深入理解java异常处理机制
  2. Java异常体系结构
文章目录
  1. 1. Java的异常
    1. 1.1. 异常分类
  2. 2. 处理异常机制
    1. 2.1. 捕捉异常
    2. 2.2. 抛出异常
    3. 2.3. return语句的执行顺序
  3. 3. 异常的转译与异常链
    1. 3.1. 异常转译
    2. 3.2. 异常链
  4. 4. 最佳实践
  5. 5. 参考