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(new FileReader(path));
try {
return br.readLine();
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);
out.close();
}
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 的示例:
// 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
和(不可见)close
方法都抛出异常,则后一个异常将被抑制(suppressed),而不是前者。 事实上,为了保留你真正想看到的异常,可能会抑制多个异常。 这些抑制的异常没有被抛弃, 而是打印在堆栈跟踪中,并标注为被抑制了。 你也可以使用 getSuppressed
方法以编程方式访问它们,该方法在 Java 7 中已添加到的 Throwable
中。
可以在 try-with-resources 语句中添加 catch 子句,就像在常规的 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 语句实际上是不可能的。
文章列表
- 高效Java编程-01. 考虑使用静态工厂方法替代构造方法
- 高效Java编程-02. 当构造方法参数过多时使用builder模式
- 高效Java编程-03. 使用私有构造方法或枚类实现Singleton属性
- 高效Java编程-04. 使用私有构造方法执行非实例化
- 高效Java编程-05. 依赖注入优于硬连接资源(hardwiring resources)
- 高效Java编程-06. 避免创建不必要的对象
- 高效Java编程-07. 消除过期的对象引用
- 高效Java编程-08. 避免使用Finalizer和Cleaner机制
- 高效Java编程-09. 使用try-with-resources语句替代try-finally语句
- 高效Java编程-10. 重写equals方法时遵守通用约定
- 高效Java编程-11. 重写equals方法时同时也要重写hashcode方法
- 高效Java编程-12. 始终重写 toString 方法
- 高效Java编程-13. 谨慎地重写 clone 方法
- 高效Java编程-14. 考虑实现Comparable接口
- 高效Java编程-15. 使类和成员的可访问性最小化
- 高效Java编程-16. 在公共类中使用访问方法而不是公共属性
- 高效Java编程-17. 最小化可变性
- 高效Java编程-18. 组合优于继承
- 高效Java编程-19. 要么设计继承并提供文档说明,要么禁用继承
- 高效Java编程-20. 接口优于抽象类
- 高效Java编程-21. 为后代设计接口
- 高效Java编程-22. 接口仅用来定义类型
- 高效Java编程-23. 类层次结构优于标签类
- 高效Java编程-24. 支持使用静态成员类而不是非静态类
- 高效Java编程-25. 将源文件限制为单个顶级类
- 高效Java编程-26. 不要使用原始类型
- 高效Java编程-27. 消除非检查警告
- 高效Java编程-28. 列表优于数组
- 高效Java编程-29. 优先考虑泛型
- 高效Java编程-30. 优先使用泛型方法
- 高效Java编程-31. 使用限定通配符来增加API的灵活性
- 高效Java编程-32. 合理地结合泛型和可变参数
- 高效Java编程-33. 优先考虑类型安全的异构容器
- 高效Java编程-34. 使用枚举类型替代整型常量
- 高效Java编程-35. 使用实例属性替代序数
- 高效Java编程-汇总