异常处理机制——捕获异常

对于受检查型异常来说,如果未对其进行异常处理,那么该程序编译不会通过,并在控制台上打印一个消息,其中包括这个异常的类型和一个堆栈轨迹。有时候为了防止程序终止,就需要捕获异常,然后做相应的处理。

异常处理有两种机制:抛出异常以及捕获异常。下文细说如何捕获异常

try catch结构

try catch结构语法如下:

try {
    // 可能发生异常的程序块
} catch (Excetpion1 e) {
    // 异常处理1
} catch (Excetpion1 e) {
    // 异常处理2
} ...
  • try:捕获异常的第一步是用try{…}语句块选定捕获异常的范围,将可能出现异常的代码放在try语句块中。

  • catch:在catch语句块中是对异常对象进行处理的代码。每个try语句块可以伴随一个或多个catch语句,用于处理可能产生的不同类型的异常对象

  • finally:捕获异常的最后一步是通过finally语句为异常处理提供一个统一的出口,使得在控制流转到程序的其它部分以前,能够对程序的状态作统一的管理

如果try语句块中的任何代码抛出了catch子句中指定的一个异常类,那么

  1. 程序将跳过try语句块剩余代码
  2. 程序将执行catch子句的处理代码

如果try语句块中的代码没有抛出任何异常,那么程序将跳过catch子句。如果方法中的任何代码抛出了子catch子句中没有声明的一个异常类型(没有捕获到异常),那么这个方法就会立即退出。

在java7中,同一个catch子句可以捕获多个异常类型。如果多个异常使用相同的处理,那么就应该用下面这种方式:

try {
    ...
} catch (ExceptionType1 e1 | ExceptionType2 e2) {
    // handler1
} catch (ExceptionType3 e3) {
    // handler3
}

什么时候需要捕获异常

很少情况需要使用主动捕获异常,绝大部分情况只需要声明异常即可,告诉调用者可能出现异常情况,让调用者自己去处理。

但下面两种情况,需要主动地去捕获异常:

  • 遇到异常,不想退出当前的方法,需要继续向下执行的情况
  • 继承父类,父类的方法没有声明异常,那么子类也不能声明异常。如果出现异常则需要去主动捕获

一种比较好的实践是:捕获那些你知道如何处理的异常,而继续传播那些你不知道如何处理的异常。

finally子句

代码抛出一个异常时,就会停止处理这个方法中剩余的代码,并退出这个方法。如果这个方法已经获得了只有它自己知道一些本地资源,而且这些资源必须清理,这就会有问题。finally子句可以解决这个问题。

不管是否有异常被捕获,finally子句中的代码都会被执行。

var in = new FileInputSteam(...);
try{
    // 1
    code that might throw exceptions
    // 2
} 
catch (IOExceptio e)
{
    // 3
    show error message
    // 4
}
finally
{
    // 5
    in.close();
}
// 6

该程序会有四种执行情况:

  • 当try子句中没有抛出异常,则执行顺序为1256
  • try子句抛出了IOExceptio异常,则执行顺序为13456
  • try子句抛出IOExceptio异常,catch中也抛出异常,则执行顺序为135
  • try子句抛出了其他异常,则执行顺序为15

获取异常信息

异常类中有两个可以获取异常信息的方法:

  • getMessage():获取异常信息,返回字符串
  • printStackTrace():获取堆栈信息,更详细的错误信息。
try {
    int[] nums = new int[] {1, 2, 3};
    int numFromNegativeIndex = nums[-1];
} catch (RuntimeException e){
    System.out.println(e.getMessage());  // Index -1 out of bounds for length 3
}
try {
    int[] nums = new int[] {1, 2, 3};
    int numFromNegativeIndex = nums[-1];
} catch (RuntimeException e){
    e.printStackTrace();
}

//java.lang.ArrayIndexOutOfBoundsException: Index -1 out of bounds for length 3
//        at com.studyjava.unit1.T3.main(T3.java:11)

在catch子句中抛出异常

可以在catch子句中抛出一个异常。通常,希望改变异常的类型时会这样做。如果开发了一个供其他程序员使用的子系统,可以使用一个指示子系统故障的异常类型,这很有道理。SeveletException就是这样一个异常类型的例子。执行一个servlet的代码可能不想知道发生错误的细节原因,但希望明确知道servlet是否有问题。

try
{
    access the database
}
catch (SQLException e)
{
    throw new SeverletException("database error" + e.getMessage());
}

上述代码有一个更好的处理方法,可以把原始异常设置为新异常的‘原因’(cause)

try 
{
    access the database
}
catch (SQLException original)
{
    var e = new ServletException("database error:" + e.getMessage());
    e.initCause(original);
    throw e;
}

捕获到这个异常时,可以getCause就可以获取原始异常:

Throwable original = caugthException.getCause();

有时候你可能只想记录一个异常,再将它重新抛出,而不做任何改变

try
{
    access the database
}
catch (Exception e)
{
    logger.log(level, message, e);
    throw e;
}