简介
try-with-resources (下简称“TWR”)是 Java 7 的新特性,是一种特殊的 try 语句,特殊性主要表现在两个方面:与原来的 try 语句在语法上有少许差异;其中定义了资源并会自动关闭它们。所谓“资源”,就是诸如流等需要关闭的对象。
《The JavaTM Tutorials》1相关示例如下:
static String readFirstLineFromFile(String path) throws IOException {
try (BufferedReader br = new BufferedReader(new FileReader(path))) {
return br.readLine();
}
}
不堪回首的过去
为什么要加入这种特性?之前是怎么样的呢?
Java 7 之前,通常使用 try-finally 语句并在 finally 中关闭资源。
虽然 finally 块被认为是关闭资源的最佳之处,但想要写出一段“完美”的资源使用代码被认为是困难的——甚至官方的示例也难免有错。
而且,如果涉及多个资源的话,资源关闭代码将变得冗长且更易出错。
总之,Java 7 之前,如果没有写出“完美”的资源使用代码,而程序仍正常运行通常只是因为异常概率极小。
基于资源的关闭操作通常都是模版化的,因此,引入 TWR 这样的语法糖就成为可能。
进一步说明
从上文的示例可以看到,与普通的 try 语句相比,TWR 的 try 关键字与代码块间多了一对圆括号,其中是要使用的资源对象的声明。要注意的是,其中声明的资源类必须是 java.lang.AutoCloseable 的实现类,否则编译错误。
要确保 TWR 生效,正确的用法是为各个资源声明独立变量。
TWR 中资源是按声明顺序相反的顺序关闭的。
TWR 语句也可以有 catch 和 finally 块,它们在声明的资源被关闭后运行。不过通常很少会这么用。
比一比
AutoCloseable 接口
TWR 中声明的资源类应实现 AutoCloseable 接口,这是 Java 7 新增的接口,它被实现为 Closeable 接口的父接口。因此,Java 7 中所有的资源类几乎都实现了 AutoCloseable 接口。
根据方法重写的限制,AutoCloseable.close() 抛出 Exception,而 Closeable.close() 抛出 IOException。
异常的抑制
传统的 try-finally 写法,try 块(资源声明及使用)和 finally 块(关闭资源)都可能抛出异常,如果都抛出异常,则 try 块中的异常将被抑制(suppressed),只能获取 finally 块中的异常信息。
对于 TWR 而言,也会有类似的情况,但是 try 块中的异常会被重抛,关闭异常会被抑制。被抑制的异常会通过 Throwable.addSuppressed() 方法增加到 try 块异常上,随后可通过调用 Throwable.getSuppressed() 方法获取被抑制的异常。(注意,这两个方法也是 Java 7 新增的。)
我们可以写一个示例来查看两种语句抛出的异常情况:
import org.testng.annotations.Test;
import java.io.IOException;
import java.io.InputStream;
public class TryResourceTest {
@Test(expectedExceptions = IOException.class, expectedExceptionsMessageRegExp = "close error")
public void testOrdinaryTry () throws IOException {
InputStream is = null;
try {
is = new MyInputStream();
is.read();
} finally {
is.close(); // 最终抛出关闭异常
}
}
@Test(expectedExceptions = IOException.class, expectedExceptionsMessageRegExp = "read error")
public void testTwr() throws IOException {
try (InputStream is = new MyInputStream()) {
is.read(); // 读取异常会被重新抛出
}
}
class MyInputStream extends InputStream {
@Override
public int read() throws IOException {
throw new IOException("read error");
}
@Override
public void close() throws IOException {
throw new IOException("close error");
}
}
}
附录
一种原始的编程技巧
普通 try 语句使用资源时,有一种常用的编程技巧,称为“双层捕获关闭流”,代码类似下面这样:
InputStream in = null;
try {
try {
// code that might throw exception
} finally {
in.close();
}
} catch(IOException e) {
// show error message
}
这样写的好处是使得 try 语句符合了单一职责原则。内层只确保关闭流,外层确保报告异常。
参考
The JavaTM Tutorials - The try-with-resources Statement
Effective Java 第三版——9. 使用try-with-resources语句替代try-finally语句
JDK1.8中的try-with-resources声明