本节描述如何使用三个异常处理程序组件(try、catch和finally块)来编写异常处理程序。然后,解释JavaSE7中引入的try-with-resources语句。try-with-resources语句特别适合于使用Closeable资源(如流)的情况。
本节的最后一部分将介绍一个示例,并分析在各种场景中发生的情况。
下面的示例定义并实现了一个名为ListOfNumbers的类。构造时,ListOfNumbers创建一个ArrayList,其中包含10个Integer元素,其顺序值为0到9。ListOfNumbers类还定义了一个名为writeList()的方法,该方法将数字列表写入名为OutFile.txt的文本文件。该示例使用java.io中定义的输出类,这些类在基本I/O部分中介绍。
// Note: This class will not compile yet.
import java.io.*;
import java.util.List;
import java.util.ArrayList;
public class ListOfNumbers {
private List<Integer> list;
private static final int SIZE = 10;
public ListOfNumbers () {
list = new ArrayList<>(SIZE);
for (int i = 0; i < SIZE; i++) {
list.add(i);
}
}
public void writeList() {
// The FileWriter constructor throws IOException, which must be caught.
PrintWriter out = new PrintWriter(new FileWriter("OutFile.txt"));
for (int i = 0; i < SIZE; i++) {
// The get(int) method throws IndexOutOfBoundsException, which must be caught.
out.println("Value at: " + i + " = " + list.get(i));
}
out.close();
}
}
粗体的第一行是对构造函数的调用。构造函数初始化文件上的输出流。如果无法打开文件,构造函数将引发IOException。第二个粗体行是对ArrayList类的get方法的调用,如果其参数的值太小(小于0)或太大(大于ArrayList当前包含的元素数),则该方法抛出IndexOutOfBoundsException。
如果尝试编译ListOfNumbers类,编译器将打印关于FileWriter构造函数引发的异常的错误消息。然而,它不会显示关于get()引发的异常的错误消息。原因是构造函数IOException引发的异常是已检查的异常,而get()方法IndexOutOfBoundsException抛出的异常是未检查的异常。
既然您已经熟悉了ListOfNumbers类,并且可以在其中抛出异常,那么就可以编写异常处理程序来捕获和处理这些异常。
?构造异常处理程序的第一步是将可能引发异常的代码封装在try块中。通常,try块如下所示:
try {
code
}
catch and finally blocks . . .
示例中标记代码的段包含一行或多行合法的代码,这些代码可能会引发异常。(catch和finally块将在接下来的两个小节中解释。)
private List<Integer> list;
private static final int SIZE = 10;
public void writeList() {
PrintWriter out = null;
try {
System.out.println("Entered try statement");
out = new PrintWriter(new FileWriter("OutFile.txt"));
for (int i = 0; i < SIZE; i++) {
out.println("Value at: " + i + " = " + list.get(i));
}
}
catch and finally blocks . . .
}
要从ListOfNumbers类中为writeList()方法构造异常处理程序,请将writeList.()方法的异常引发语句封装在try块中。有多种方法可以做到这一点。您可以将可能引发异常的每一行代码放在其自己的try块中,并为每一行提供单独的异常处理程序。或者,您可以将所有writeList()代码放在单个try块中,并将多个处理程序与之关联。下面的列表对整个方法使用一个try块,因为所讨论的代码非常短。
如果在try块中发生异常,则该异常由与其关联的异常处理程序处理。要将异常处理程序与try块相关联,必须在其后面放置catch块;下一节“catch Blocks”将向您展示如何进行。
通过在try块之后直接提供一个或多个catch块,可以将异常处理程序与try块相关联。在try块的末尾和第一个catch块的开头之间不能有代码。
try {
} catch (ExceptionType name) {
} catch (ExceptionType name) {
}
每个catch块都是一个异常处理程序,用于处理由其参数指示的异常类型。参数类型ExceptionType声明处理程序可以处理的异常类型,并且必须是从Throwable类继承的类的名称。处理程序可以引用名为的异常。
catch块包含在调用异常处理程序时执行的代码。当处理程序是调用堆栈中ExceptionType与引发的异常类型匹配的第一个处理程序时,运行时系统调用异常处理程序。如果抛出的对象可以合法地分配给异常处理程序的参数,则系统将其视为匹配。
下面是writeList()方法的两个异常处理程序:
try {
} catch (IndexOutOfBoundsException e) {
System.err.println("IndexOutOfBoundsException: " + e.getMessage());
} catch (IOException e) {
System.err.println("Caught IOException: " + e.getMessage());
}
异常处理程序可以做的不仅仅是打印错误消息或暂停程序。它们可以执行错误恢复,提示用户做出决策,或者使用链接异常将错误传播到更高级别的处理程序,如“链接异常”部分中所述。
?使用多捕获模式,可以使用一个异常处理程序捕获多个类型的异常。
在JavaSE7和更高版本中,单个catch块可以处理多种类型的异常。此功能可以减少代码重复,并减少捕获过宽异常的诱惑。
在catch子句中,指定块可以处理的异常类型,并用竖线(|)分隔每个异常类型:
catch (IOException|SQLException ex) {
logger.log(ex);
throw ex;
}
注意:如果catch块处理多个异常类型,则catch参数隐式为final。在本例中,catch参数ex是final,因此不能在catch块中为其分配任何值。
当try块退出时,finally块始终执行。这确保即使发生意外异常,也会执行finally块。但finally不仅仅用于异常处理——它允许程序员避免返回、继续或中断意外绕过清理代码。将清理代码放在finally块中始终是一种良好的实践,即使预期没有异常。
注意:如果JVM在执行try或catch代码时退出,则finally块可能不会执行。
您在这里使用的writeList()方法的try块将打开PrintWriter。程序应在退出writeList()方法之前关闭该流。这带来了一个有点复杂的问题,因为writeList()的try块可以以三种方式之一退出。?
运行时系统始终在finally块中执行语句,而不管try块中发生了什么。因此,它是执行清理的完美场所。
下面的writeList()方法的finally块将清理,然后关闭PrintWriter。
finally {
if (out != null) {
System.out.println("Closing PrintWriter");
out.close();
} else {
System.out.println("PrintWriter not open");
}
}
?要点:finally块是防止资源泄漏的关键工具。关闭文件或以其他方式恢复资源时,请将代码放在finally块中,以确保始终恢复资源。
在这些情况下,请考虑使用try-with-resources语句,该语句会在不再需要时自动释放系统资源。try with resources语句部分包含更多信息。