Java类库里包含了必须通过调用close方法来手动关闭的资源。比如InputStream,OutputStream还有java.sql.Connection。
关闭资源这个动作通常被客户端忽视了,其性能表现也可想而知。虽然大部分这些资源都使用终结方法作为最后的安全线,但终结方法的效果并不是很好。
在过去(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-finally语句的代码,如前面所示,也有个不起眼的缺点。
无论是try里面的代码还是finally里面的代码,都有可能抛出异常。
无论是try里面的代码还是finally里面的代码,都有可能抛出异常。例如,在firstLineOfFile方法里,如果底层物理设备出了故障,则在调用readLine方法时会抛出异常,而且由于相同的原因,调用close方法也会失败。
在这种情况下,第二种异常覆盖了第一种异常。在异常错误栈里将没有第一种异常的记录,这会使实际系统的调试变得很复杂,因为很多时候你是想查看第一种异常来诊断问题。(异常屏蔽的情况)
当 Java 7引入try-with-resources
语句时,所有问题突然一下子解决了。
若要使用这个语句,一个资源必须实现AutoCloseable接口,而这个接口只有一个返回类型为void的close(void-returning)方法。
意味着可能抛出 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 中一种新的用于安全管理资源的语言结构。这种扩展带来的影响不仅仅是更多的语法糖。事实上,它能位开发人员生成了正确的代码,消除了编写容易出错的样板代码的需要。更重要的是,这种变化还伴随着将一个异常附加到另一个异常的改进,从而为众所周知的异常彼此屏蔽问题提供了完善的解决方案。