java类库中包括许多必须通过调用close方法来手工关闭的资源。例如InputStream、OutputStream...,客户端经常会忘记关闭资源,造成严重的性能后果。虽然这其中的许多资源都是用终结方法(慎用终结方法)作为安全网,但是效果并不理想。
1、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(); } }
但是如果再添加第二个资源,就会一团糟
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[FileCopyUtils.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块都会抛出异常。例如,在firstLineOfFile方法中,如果底层的物理设备异常,那么调用readLine就会抛出异常,基于同样的原因,调用close也会发生异常。在这种情况下,第二个异常完全抹除了第一个异常,在异常堆栈轨迹中,完全没有第一个异常的记录,这在现实的系统中,会导致调试变得非常复杂。虽然可以通过编写代码来禁止第二个异常,保留第一个异常,但是事实上实现起来太繁琐了。
当是使用try-with-resources语句时,这些问题就全部解决了。
2、try-with-resources
要使用这个构造的资源,必须先实现AutoCloseable接口,其中包含单个返回void和close方法。
以下使用try-with-resources改造第一个示例
static String firstLineOfFile(String path) throws IOException { try (BufferedReader br = new BufferedReader(new FileReader(path))) { return br.readLine(); } }
一下改造第二个示例
static void copy(String src,String dst) throws IOException{ try(InputStream in = new FileInputStream(src); OutputStream out = new FileOutputStream(dst)) { byte[] buf = new byte[FileCopyUtils.BUFFER_SIZE]; int n; while ((n = in.read(buf))>0) { out.write(buf,0,n); } } }
使用 try-with-resources不仅是代码变得更简洁易懂,也更容易诊断。以firstLineOfFile方法为例,如果调用readLine和(不可见的)close方法都抛出异常,后一个异常就会被禁止,已保留第一个异常。
为了保留你想看到的异常,即便多个异常被禁止,这些异常并不是简单的被抛弃了,而是会打印在堆栈轨迹中,并注明它们是被禁止的异常。通过调用getSuppressed方法还可以访问到它们。