程序在运行时总是会出现各种各样的错误,一个健壮的程序必须要能够处理各种各样的错误。在Java中,错误恢复机制尤其重要,因为Java的主要目标之一就是创建供他人使用的程序构件。
异常
在C语言中,异常并不是语言本身的一部分,对于一些错误的处理,通常是约定俗成返回某个值来确定是否发生错误,例如某个函数打开文件成功就返回1,否则返回0。但是这种方式到了要处理错误的时候就会非常麻烦,处理错误变成了让程序员非常痛苦的事情。
Java在语言层面使用强制规定的形式来进行异常处理,当异常出现的时候,你发现当前还无法解决这个问题,但也不能放任不管,可以停下来,将这个错误抛向更高级的环境中去。
异常可以降低程序错误处理的复杂度,使用异常,你可以不必立刻去检查错误的类型,在程序中处理。我们通过异常捕获错误,理想情况下,将错误丢给特定的异常处理程序去处理,这样我们就将正常情况下要做的事和出错的情况下要做的事分离开来。
与以前的错误处理方法相比,异常机制使代码的阅读、编写和调试工作更加井井有条。
异常情形(exceptional condition
)是指阻止当前方法或作用域继续执行的问题。例如文件未找到问题,在这种情况下,当前环境无法解决问题,就不能让程序继续下去,要跳出当前环境,将问题交给上级环境解决。
异常允许我们(如果没有其他手段)强制程序停止运行,并告诉我们出现了什么问题,或者(理想状态下)强制程序处理问题,并返回到稳定状态。
抛出异常
在Java中,异常也是class
。继承关系如下图:
所有异常都继承自Throwable
,它的两个子类中,Error
表示一些严重的错误,程序一般对此无能为力,例如StackOverflowError
。Exception
则是运行中的错误,可以被捕获并处理。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) {
// ...
}
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。