Java类库里包含了必须通过调用close方法来手动关闭的资源。比如InputStream,OutputStream还有java.sql.Connection。
关闭资源这个动作通常被客户端忽视了,其性能表现也可想而知。虽然大部分这些资源都使用终结方法作为最后的安全线,但终结方法的效果并不是很好。
Java SE 7之前
在过去(Java7之前)的实践当中,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();
} finally {
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);
} finally {
out.close();
}
} finally {
in.close();
}
}复制代码
try-finall缺点
即使对于正确使用了try-finally语句的代码,如前面所示,也有个不起眼的缺点。无论是try里面的代码还是finally里面的代码,都有可能抛出异常。
无论是try里面的代码还是finally里面的代码,都有可能抛出异常。例如,在firstLineOfFile方法里,如果底层物理设备出了故障,则在调用readLine方法时会抛出异常,而且由于相同的原因,调用close方法也会失败。在这种情况下,第二种异常覆盖了第一种异常。在异常错误栈里将没有第一种异常的记录,这会使实际系统的调试变得很复杂,因为很多时候你是想查看第一种异常来诊断问题。(异常屏蔽的情况)
Java SE 7之后
当Java 7引入try-with-resources
语句时,所有问题突然一下子解决了。
实现
若要使用这个语句,一个资源必须实现AutoCloseable接口,而这个接口只有一个返回类型为void的close(void-returning)方法。
java.lang.AutoCloseable 接口中 close() 方法的定义意味着可能抛出 java.lang.Exception。
然而,前面的 AutoClose 示例对该方法进行声明,但并未提及任何检查到的异常,这是我们有意为之,部分是为了说明异常屏蔽。
Java类库和第三方类库里面的许多类和接口现在都实现或继承了AutoCloseable接口。如果你写了一个类,这个类代表一个必须被关闭的资源,那么你的类也应该实现AutoCloseable接口。
建议
可自动关闭类的规范建议避免抛出
java.lang.Exception,优先使用具体的受检异常,如果预计 close() 方法不会失败,就不必提及任何受检异常。此外还建议,不要声明任何不应被抑制的异常,java.lang.InterruptedException 就是最好的例子。
实际上,抑制该异常并将其附加到另一个异常可能会导致忽略线程中断事件,使应用程序处于不一致的状态。
这是我们自主实现AutoCloseable的一个例子:
/**
* 资源类
*/public class Resource implements AutoCloseable {
public void invoke() {
// todo 业务处理
System.out.println("Resource is using");
}
@Override
public void close() throws Exception {
// todo 关闭资源
System.out.println("Resource is closed");
}
}复制代码
public class CloseResource {
public static void main(String[] args) {
try(Resource resource = new Resource()) {
resource.invoke();
} catch (Exception e) {
e.printStackTrace();
}
}
}复制代码
下面这个例子展示了如何使用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-finally语句那样,往try-with-resources里面添加catch子句。
这能让我们无需在另一层嵌套污染代码就能处理异常。下面是一个比较刻意的例子,这个版本中的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 SE 7 中一种新的用于安全管理资源的语言结构。这种扩展带来的影响不仅仅是更多的语法糖。事实上,它能位开发人员生成了正确的代码,消除了编写容易出错的样板代码的需要。更重要的是,这种变化还伴随着将一个异常附加到另一个异常的改进,从而为众所周知的异常彼此屏蔽问题提供了完善的解决方案。