异常处理机制——捕获异常
对于受检查型异常来说,如果未对其进行异常处理,那么该程序编译不会通过,并在控制台上打印一个消息,其中包括这个异常的类型和一个堆栈轨迹。有时候为了防止程序终止,就需要捕获异常,然后做相应的处理。
异常处理有两种机制:抛出异常以及捕获异常。下文细说如何捕获异常
try catch结构
try catch结构语法如下:
try {
// 可能发生异常的程序块
} catch (Excetpion1 e) {
// 异常处理1
} catch (Excetpion1 e) {
// 异常处理2
} ...
-
try:捕获异常的第一步是用try{…}语句块选定捕获异常的范围,将可能出现异常的代码放在try语句块中。
-
catch:在catch语句块中是对异常对象进行处理的代码。每个try语句块可以伴随一个或多个catch语句,用于处理可能产生的不同类型的异常对象
-
finally:捕获异常的最后一步是通过finally语句为异常处理提供一个统一的出口,使得在控制流转到程序的其它部分以前,能够对程序的状态作统一的管理
如果try语句块中的任何代码抛出了catch子句中指定的一个异常类,那么
- 程序将跳过try语句块剩余代码
- 程序将执行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;
}