解锁Python编程的无限可能:《奇妙的Python》带你漫游代码世界
在Java编程中,许多资源(如 InputStream
、OutputStream
和 java.sql.Connection
)必须在使用后手动关闭。然而,开发者常常忘记关闭这些资源,导致严重的性能问题。虽然终结器(详见【条目8】)曾经被用作安全网,但它们并不可靠。在 Java 7 之前,try-finally
语句是确保资源正确关闭的唯一方式,即使在异常或提前返回时,try-finally
也能保证资源关闭:
// 使用 try-finally 关闭资源 - 不再是最佳方式
static String firstLineOfFile(String path) throws IOException {
BufferedReader br = new BufferedReader(new FileReader(path));
try {
return br.readLine();
} finally {
br.close();
}
}
虽然这种方式看起来并不坏,但在处理多个资源时,它会变得非常繁琐:
// 使用 try-finally 处理多个资源时代码显得复杂
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();
}
}
即使是优秀的程序员,也经常在 try-finally
中犯错。事实上,Java 库中有三分之二的 close
方法调用在2007年是错误的。
使用 try-finally 的问题
即使 try-finally
可以正确关闭资源,但它存在一个微妙的问题:try
块和 finally
块中的代码都可能抛出异常。例如,在 firstLineOfFile
方法中,readLine
方法可能由于设备故障抛出异常,而 close
方法也可能因此失败。在这种情况下,后续的异常会掩盖第一个异常,导致调试变得非常困难。虽然可以编写代码来抑制第二个异常,但几乎没有人会这么做,因为代码会变得冗长复杂。
Java 7 的解决方案:try-with-resources
Java 7 引入了 try-with-resources
语句,解决了 try-finally
的所有问题。要使用该语句,资源类必须实现 AutoCloseable
接口,该接口只有一个返回 void
的 close
方法。Java 库和第三方库中的许多类现在都实现了该接口。以下是使用 try-with-resources
重写的 firstLineOfFile
方法:
// 使用 try-with-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 处理多个资源 - 简洁明了
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 的优势
与 try-finally
相比,try-with-resources
更加简洁且易于维护。它不仅减少了冗余的代码,还提供了更好的异常处理机制。如果在 try
块和 close
方法中同时抛出异常,try-with-resources
会抑制后续异常,并保留最重要的第一个异常。抑制的异常不会被简单忽略,而是记录在异常堆栈中,并带有“抑制异常”的说明。你可以通过 Throwable.getSuppressed
方法程序化地访问这些抑制的异常。
你也可以像在普通 try-finally
中一样,为 try-with-resources
添加 catch
子句来处理异常。如下所示是一个不抛出异常的 firstLineOfFile
版本,它在无法打开文件时返回默认值:
// 带有 catch 子句的 try-with-resources
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
的优势明显,强烈建议在需要关闭资源的场景下使用这一语句。