程序在运行时总是会出现各种各样的错误,一个健壮的程序必须要能够处理各种各样的错误。在Java中,错误恢复机制尤其重要,因为Java的主要目标之一就是创建供他人使用的程序构件。

异常

在C语言中,异常并不是语言本身的一部分,对于一些错误的处理,通常是约定俗成返回某个值来确定是否发生错误,例如某个函数打开文件成功就返回1,否则返回0。但是这种方式到了要处理错误的时候就会非常麻烦,处理错误变成了让程序员非常痛苦的事情。

Java在语言层面使用强制规定的形式来进行异常处理,当异常出现的时候,你发现当前还无法解决这个问题,但也不能放任不管,可以停下来,将这个错误抛向更高级的环境中去。

异常可以降低程序错误处理的复杂度,使用异常,你可以不必立刻去检查错误的类型,在程序中处理。我们通过异常捕获错误,理想情况下,将错误丢给特定的异常处理程序去处理,这样我们就将正常情况下要做的事出错的情况下要做的事分离开来。

与以前的错误处理方法相比,异常机制使代码的阅读、编写和调试工作更加井井有条。

异常情形exceptional condition)是指阻止当前方法或作用域继续执行的问题。例如文件未找到问题,在这种情况下,当前环境无法解决问题,就不能让程序继续下去,要跳出当前环境,将问题交给上级环境解决。

异常允许我们(如果没有其他手段)强制程序停止运行,并告诉我们出现了什么问题,或者(理想状态下)强制程序处理问题,并返回到稳定状态。

抛出异常

在Java中,异常也是class。继承关系如下图:

异常.png

所有异常都继承自Throwable,它的两个子类中,Error表示一些严重的错误,程序一般对此无能为力,例如StackOverflowErrorException则是运行中的错误,可以被捕获并处理。Exception及其除RuntimeException外的子类,是Java规定必须捕获的异常。

public void test(String s) {
    if (s == null) {
         NullPointerException e = new NullPointerException();
         throw e;
    }
}

异常类的使用和其它类没有区别,只是使用throw抛出。这两行也可以简写成一行throw new NullPointerException();

异常捕获

如果在某个方法内部抛出了异常,或者方法中调用的某个方法抛出了异常,这个方法会在抛出异常的过程中结束。如果不希望它就此结束,就可以在方法中设置一个块,在这个块中使用可能发生异常的方法,在Java中使用try关键字来实现。

try {
    // 可能发生异常的操作
}

在不支持异常的语言里,要想检测错误,要在方法调用前后加上各种错误检查的代码,使用异常,将代码放到try中,然后只需在一个地方就可以捕获所有异常。代码可读性也会提高,因为程序的意图和错误检查是分开的。

抛出的异常必须在某个地方得到处理,这就是我们的异常处理程序,使用catch关键字:

try {
    // Code that might generate exceptions
} catch(Type1 id1) {
    // Handle exceptions of Type1
} catch(Type2 id2) {
    // Handle exceptions of Type2
} catch(Type3 id3) {
    // Handle exceptions of Type3
}
// etc.

例如读取文件:

public void read(String filename) {
    try {
        InputStream in = new FileInputStream(filename);
        int b;
        while ((b = in.read()) != -1) {
            process input
        }
    } catch (IOException exception) {
        exception.printStackTrace();
    }
}

当发生IOException异常时,就会执行catch块中的内容,注意,如果有多个catch,只要匹配到一个异常,就不会再执行后面的catch,所以如果你的代码中有多个异常,并且有父子类关系,那么一定要把子类放前面(可以思考一下原因)。

public void read(String filename) throws IOException {
    InputStream in = new FileInputStream(filename);
    int b;
    while ((b = in.readO) != -1) {
        process input
        }
}

如上代码,在定义的方法中加上throws IOException,这样我们可以把对异常的处理丢给read方法的调用者去操心,编译器要求必须对有throws说明的方法调用抛出的异常进行处理。

有些时候,我们希望执行一些操作,不过有没有异常都执行:

public void test() {
    try {
        // ...
        System.out.println("结束,回收资源");
    } catch (IOException exception) {
        // ...
        System.out.println("结束,回收资源");
    }
}

像上面这样写,非常费事,可以使用finally关键字:

public void test() {
    try {
        // ...
    } catch (IOException exception) {
        // ...
    } finally {
        System.out.println("结束,回收资源");
    }
}

不过有没有抛出异常,finally都一定会执行,这可以保证那些使用了某些资源的程序能完成资源回收。

对于一些有着相同处理代码但是又没有继承关系的异常类(子类继承于父类,那么用父类就可以表示子类异常,这也是前面说catch子类要写在前面的原因),从Java7开始,可以这样写:

try {
    // ...
    } catch(Except1 | Except2 | Except3 | Except4 e) {
            // ...
        }

二维码.jpg


公子政
39 声望7 粉丝

在求知的道路上不断前行