Java高效编程(9):优先使用 try-with-resources 而非 try-finally**
解锁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 的优势明显,强烈建议在需要关闭资源的场景下使用这一语句。
