ITEM 9: PREFER TRY-WITH-RESOURCES TO TRY-FINALLY
Java库包含许多必须通过调用close方法手动关闭的资源,如 InputStream, OutputStream, java.sql.Connection.。关闭资源常常被用户忽视,其后果可想而知。虽然这些库包中有许多使用 finalizer 作为安全网,但是 finalizer 并不能很好地工作(第8项)。
通常,try-finally 语句是确保资源被正确关闭的最佳方法,即使在出现异常或返回时也是如此:
// try-finally - No longer the best way to close resources!
static String firstLineOfFile(String path) throws IOException {
BufferedReader br = new BufferedReader(newFileReader(path));
try {
return br.readLine();
} finally {
br.close();
}
}
这看起来不错,但是当你添加第二个资源时,情况就更糟了:
// try-finally is ugly when used with more than one resource!
static void copy(String src, String dst) throws IOException {
InputStream in = new FileInputStream(src);
try {
OutputStream out = new FileOutputStream(dst);
try {
byte[] buf = new byte[BUFFER_SIZE]; int n;
while ((n = in.read(buf)) >= 0)
out.write(buf, 0, n);
} finally {
out.close();
}
} finally {
in.close();
}
}
这可能很难相信,但即使是优秀的程序员在大多数情况下也会犯这样的错误。首先,我在Java Puzzlers [Bloch05] 的88页上做错了,多年来没有人注意到这一点。事实上,2007年Java库中使用close方法的三分之二是错误的。即使这样使用 try-finally 语句关闭资源的正确代码(如前两个代码示例所示)也有一个细微的缺陷:try 块和finally 块中的代码都能够抛出异常。例如,在firstLineOfFile 方法中,对 readLine 的调用可能会由于底层物理设备中的故障引发异常,而对 close 的调用也可能因为相同的原因而失败。在这种情况下,第二个异常完全覆盖了第一个异常。在异常堆栈跟踪中没有关于第一个异常的记录,这可能会使系统中的调试变得非常复杂 —— 通常,为了诊断问题,您希望看到的是第一个异常。虽然可以编写代码来抑制第二个异常同时支持第一个异常,但实际上没有人这样做,因为它太冗长了。
当Java 7 引入 try-with-resources 语句时,所有这些问题都一下子解决了。要使用这个结构,资源必须实现 AutoCloseable 接口,该接口包含一个返回 void 的close方法。Java库和第三方库中的许多类和接口现在都实现或扩展了 AutoCloseable。如果您编写了一个表示必须关闭的资源的类,那么您的类也应该实现AutoCloseable。
下面是第一个使用try-with-resources的示例:
// try-with-resources - the the best way to close resources!
static String firstLineOfFile(String path) throws IOException {
try (BufferedReader br = new BufferedReader(
new FileReader(path))) {
return br.readLine();
}
}
下面是第二个使用try-with-resources的示例:
// try-with-resources on multiple resources - short and sweet
static void copy(String src, String dst) throws IOException {
try (InputStream in = new FileInputStream(src);
OutputStream out = new FileOutputStream(dst)) {
byte[] buf = new byte[BUFFER_SIZE];
int n;
while ((n = in.read(buf)) >= 0)
out.write(buf, 0, n);
}
}
与原始版本相比,TRY-WITH-RESOURCES 版本不仅更短、更易于阅读,而且提供了更好的调式信息。考虑 firstLineOfFile 方法。如果 readLine 调用和(不可见的)关闭都抛出异常,则后一个异常将被抑制,以支持前一个异常。实际上,可能会抑制多个异常,以便保留您实际上想要看到的异常。这些被抑制的异常不是简单地被抛弃,它们还被打印在堆栈跟踪中,并带有表示它们被抑制的符号。您还可以使用getsuppress 方法以编程方式访问它们,该方法是在Java 7中添加到Throwable中的。
您可以将 catch 子句放在 try-with-resources 语句上,就像您可以放在常规try-finally 语句上一样。这允许您处理异常,而不需要使用另一层嵌套破坏代码。作为一个稍微有点做作的例子,下面是我们的 firstLineOfFile 方法的一个版本,它不会抛出异常,但是如果它不能打开文件或从文件中读取,它会返回一个默认值:
// try-with-resources with a catch clause
static String firstLineOfFile(String path, String defaultVal) {
try (BufferedReader br = new BufferedReader(
new FileReader(path))) {
return br.readLine();
} catch (IOException e) {
return defaultVal;
}
}
在处理必须关闭的资源时,始终使用资源 try-with-resources 而不是 try-finally。生成的代码更短、更清晰,并且生成的异常更有用。try-with-resources 语句使得使用必须关闭的资源编写正确的代码变得很容易,而使用 try-finally 实际上是不可能的。
网友评论