转载

《Effective Java》-- 使用try-with-resources关闭资源

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 中一种新的用于安全管理资源的语言结构。这种扩展带来的影响不仅仅是更多的语法糖。事实上,它能位开发人员生成了正确的代码,消除了编写容易出错的样板代码的需要。更重要的是,这种变化还伴随着将一个异常附加到另一个异常的改进,从而为众所周知的异常彼此屏蔽问题提供了完善的解决方案。

原文  https://juejin.im/post/5e9d07f56fb9a03c4237ac25
正文到此结束
Loading...