抛出异常的过程十分容易,只要将其抛出就不用理睬了。当然,有些代码必须捕获异常,且捕获异常需要进行周密的计划。
捕获异常
如果某个异常发生的时候没有在任何地方进行捕获,那程序就会终止执行,并在控制台上打印出异常信息,其中包括异常的类型和堆栈的内容。
要想捕获一个异常,必须设置try/catch语句块。简单的try语句块如下:
try {
code...
....
} catch (ExceptionType e) {
handler for this type
}
如果在try语句块中的任何代码抛出了一个在catch字句中说明的异常类,那么:
- 程序将跳过try语句块的其余代码。
- 程序将执行catch字句中的处理器代码
如果在try语句块中的代码没有抛出任何异常,那么程序将跳过catch字句。
如果方法中的任何代码抛出了一个在catch字句中没有声明的异常类型,那么这个方法就会立刻退出。
为了演示捕获异常的处理过程,我们看一个读取数据的典型程序代码:
public void read(String filename) {
try {
InputStream in = new FileInputStream(filename);
int b;
while ((b = in.read()) != -1)
...
} catch (IOException exception) {
exception.printStrackTrace();
}
}
需要注意的是,try语句中的大多数代码都很容易理解:读取并处理字节,知道遇到文件结束符为止。正如Java API中看到的那样,read方法有可能抛出一个IOException异常。在这种情况下,将跳出整个while循环,进入catch字句,并生成一个栈轨迹。对于一个普通的程序来说,这样处理异常基本上合乎情理。还有其他的选择吗?
通常,最好的选择是什么也不做,而是将异常传递给调用者。如果read方法出现了错误,就让read方法的调用者去操心。如果采用这种处理方式,就必须声明这个方法可能会抛出一个IOException。
public void read(String filename) throws IOExcepton {
InputStream in = new FileInputStream(filename);
int b;
while ((b = in.read()) != -1)
...
}
请记住,编译器严格地执行throws说明符。如果调用了一个抛出受查异常的方法,就必须对它进行处理,或者继续传递。
哪种方法更好呢?通常,应该捕获那些知道如何处理的异常,而将那些不知道怎么处理的异常继续进行传递。
如果想传递一个异常,就必须在方法的首部添加一个throws说明符,以便告知调用者这个方法可能会抛出异常。
这个规则也有一个例外,如果编写一个覆盖超类的方法,而这个方法又没有抛出异常,那么这个方法就必须捕获方法代码中出现的每一个受查异常。不允许在子类的throws说明符中出现超类方法所列出的异常类范围。
捕获多个异常
在一个try语句块中可以捕获多个异常类型,并对不同的异常做出不同的处理。可以按照下列方式为每个异常类型使用一个单独的catch字句:
try {
code that might throw exceptions
} catch (FileNotFoundExcetpion e) {
...
} catch (UnknownHostException e) {
...
} catch (IOException e) {
...
}
异常对象可能包含与异常本身有关的信息。要想获得对象的更多信息,可以试着使用
e.getMessage()
得到详细的错误信息,或者使用
e.getClass().getName()
得到异常对象的实际类型。
在Java SE7中,同一个catch字句可以捕获多个异常类型。如:
try {
code that might throw exceptions
} catch (FileNotFoundException | UnknownHostException e) {
...
} catch (IOException e) {
...
}
只有当捕获的异常类型彼此之间不存在子类关系时才需要这个特性。
再次抛出异常与异常链
在catch子句中可以抛出一个异常,这样做的目的是改变异常的类型。如果开发了一个供其他程序员使用的子系统,那么,用于表示子系统故障的异常类型可能会产生多种解释。ServletException就是这样一个异常类型的例子。执行servlet的代码可能不想知道发生错误的细节原因,但希望明确地知道servlet是否有问题。如:
try {
access the database
} catch (SQLException e) {
throw new ServletException("database error:" + e.getMessage());
}
这里,ServletException用带有异常信息文本的构造器来构造。
不过,可以有一种更好的处理方法,并且将原始异常设置为新异常的“原因”:
try {
access the database
} catch (SQLException e) {
Throwable se = new ServletException("database error");
se.initCause(e);
throw se;
}
当捕获到异常时,就可以使用下面这条语句重新得到原始异常:
Throwable e = se.getCause();
强烈建议使用这种包装技术。这样可以让用户抛出子系统中的高级异常,而不会丢失原始异常的细节。
有时你可能只想记录一个异常,而将它重新抛出,而不做任何改变:
try {
access the database
} catch (Exception e) {
logger.log(level, message, e);
throw e;
}
在Java SE 7之前,这种方法存在一个问题,假设这个代码在以下方法中:
public void updateRecord() throws SQLException
Java编译器查看catch块中的throw语句,然后查看e的类型,会指出这个方法可以抛出任何Exception而不只是SQLException。
现在这个问题已经有所改进。编译器会跟踪到e来自try块。假设这个try块中仅有的已检查异常是SQLException实例,另外,假设e在catch块中未改变,将外围方法声明为throws SQLException就是合法的。
捐赠
若你感觉读到这篇文章对你有启发,能引起你的思考。请不要吝啬你的钱包,你的任何打赏或者捐赠都是对我莫大的鼓励。