Effective Java 3rd 条目9 try-with-resource优于try-finally

Java库包含很多资源,这些资源必须手动调用close方法关闭。这样的例子包括InputStream、OutputStream,和java.sql.Connection。关闭资源常常被客户端忽略,可以预见会有可怕的性能后果。许多这些资源用finalizer作为安全保障,但是finalizer不会很好的工作(条目8)。

历史上,一个资源应该正确地关闭,即使面对一个异常或者返回,try-finally语句是最好的保证方式:

// try-finally - 不再是关闭资源最好的方式!
static String firstLineOfFile(String path) throws IOException { 
    BufferedReader br = new BufferedReader(new FileReader(path)); 
    try { 
        return br.readLine(); 
    } finally { 
        br.close(); 
    } 
}

这可能看上去不太糟,但是当你添加第二个资源时会变得更糟:

// 当用于多于一个资源时,try-finally 是丑陋的! 
static void copy(String src, String dst) throws IOException {       
    try { 
        InputStream in = new FileInputStream(src); 
        try { 
            OutputStream out = new FileOutputStream(dst); 
            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(); 
    }
}

这可能难于置信,但是优秀的程序员甚至多数时间会把这个弄错。一开始,我在Java Puzzlers [Bloch05] 88页把这个弄错了,而且几年没人注意到。事实上在2007年,Java库的close方法的使用,三分之二是错误的。

用try-finally语句关闭资源的正确代码,就像前面两个代码例子,甚至有微妙的缺陷。在两个try代码块和finally代码块中的代码可能会抛出异常。比如,firstLineOfFile方法中,由于底层物体设备的失败,调用readLine可能抛出一个异常,而且close方法调用可能因为同样的原因失败。在这些情形下,第二个异常完全掩盖了第一个。在异常栈信息中没有第一个异常的记录,在实际系统中这个可能使得调试非常复杂,通常为了诊断这个问题,第一个异常是我们想要看见的。为了第一个异常而抑制第二个,虽然编写这样的代码是可能的,事实上,没有人这么干,因为这太啰嗦了。

当Java 7引进了try-with-resource语句[JLS, 14.20.3],这些问题全解决了。为了用在这个结构,一个资源必须实现AutoCloseable接口,这个接口包含一个返回空的close方法。Java库和第三方库中的许多类和接口现在已经实现或者扩展了AutoCloseable。如果你编写一个必须关闭资源的类,你的类也应该实现AutoCloseable。

下面是我们第一个例子使用try-with-resource的样子:

// try-with-resources - 关闭资源的最好方式!
static String firstLineOfFile(String path) throws IOException {
    try (BufferedReader br = new BufferedReader( 
            new FileReader(path))) {
        return br.readLine();
    } 
}

下面是我们第二个例子使用try-with-resource的样子:

// 多个资源的try-with-resources - 简短而明了
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]; 
        while ((n = in.read(buf)) >= 0) 
            out.write(buf, 0, n); 
        }
}

不仅try-with-resource版本比原来更加简短和可读,而且它们提供了好得多的可诊断性。考虑firstLineOfFile方法。如果异常由两个readLine调用和(不可见的)close方法抛出,为了前面的异常,后面的抑制了。事实上,为了保护你真实想看到的异常,多个异常可以被抑制。这些抑制的异常没有被丢弃,它们打印在在一个堆栈信息里面,用一个注释说明它们被抑制了。你也可以用getSuppressed方法以编程方式获取它们,在Java 7中这个方法添加到了Throwable。

你可以把catch子句放在try-with-resource语句,就像你可以放在try-finally语句一样。这让你可以处理异常,而没有用另外一层嵌套来污染你的代码。作为有点特别的例子,下面是firstLineOfFile方法的一个版本,它不抛出异常,但是如果不能够打开或者读取文件,它返回一个默认值:

// 有catch子句的try-with-resource 
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-resource,而不是try-finally。这样的代码更加简短和清晰,而且它产生的异常也更加有用。对于使用必须关闭的资源的代码,try-with-resource语句使得正确编写这些代码更容易,而用try-finally是在实践中不可能的。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值