effective-java-3rd-chinese/09. 使用try-with-resources语句替代try-finally语句.md

93 lines
5.3 KiB
Markdown
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 9. 使用 try-with-resources 语句替代 try-finally 语句
---
  Java 类库中包含许多必须通过调用 `close` 方法手动关闭的资源。 比如 `InputStream``OutputStream` 和 `java.sql.Connection`。 客户经常忽视关闭资源,其性能结果可想而知。 尽管这些资源中有很多使用 finalizer 机制作为安全网,但 finalizer 机制却不能很好地工作(条目 8
  从以往来看try-finally 语句是保证资源正确关闭的最佳方式,即使是在程序抛出异常或返回的情况下:
```Java
// try-finally - No longer the best way to close resources!
static String firstLineOfFile(String path) throws IOException {
BufferedReader br = new BufferedReader(new FileReader(path));
try {
return br.readLine();
} finally {
br.close();
}
}
```
  这可能看起来并不坏,但是当添加第二个资源时,情况会变得更糟:
```Java
// 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-with-resources 块和 finally 块中的代码都可以抛出异常。 例如,在 `firstLineOfFile` 方法中,由于底层物理设备发生故障,对 `readLine` 方法的调用可能会引发异常,并且由于相同的原因,调用 `close` 方法可能会失败。 在这种情况下,第二个异常完全冲掉了第一个异常。 在异常堆栈跟踪中没有第一个异常的记录,这可能使实际系统中的调试非常复杂——通常这是你想要诊断问题的第一个异常。 虽然可以编写代码来抑制第二个异常,但是实际上没有人这样做,因为它太冗长了。
  当 Java 7 引入了 try-with-resources 语句时,所有这些问题一下子都得到了解决[JLS,14.20.3]。要使用这个构造,资源必须实现 `AutoCloseable` 接口,该接口由一个返回为 `void``close` 组成。Java 类库和第三方类库中的许多类和接口现在都实现或继承了 `AutoCloseable` 接口。如果你编写的类表示必须关闭的资源,那么这个类也应该实现 `AutoCloseable` 接口。
  以下是我们的第一个使用 try-with-resources 的示例:
```Java
// 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 的示例:
```Java
// 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` 和(不可见)`close` 方法都抛出异常则后一个异常将被抑制suppressed而不是前者。 事实上,为了保留你真正想看到的异常,可能会抑制多个异常。 这些抑制的异常没有被抛弃, 而是打印在堆栈跟踪中,并标注为被抑制了。 你也可以使用 `getSuppressed` 方法以编程方式访问它们,该方法在 Java 7 中已添加到的 `Throwable` 中。
  可以在 try-with-resources 语句中添加 catch 子句,就像在常规的 try-finally 语句中一样。这允许你处理异常,而不会在另一层嵌套中污染代码。作为一个稍微有些做作的例子,这里有一个版本的 `firstLineOfFile` 方法,它不会抛出异常,但是如果它不能打开或读取文件,则返回默认值:
```Java
// 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 语句实际上是不可能的。