try-with-resource从入门到使用

try-with-resource从入门到使用

try{
	资源打开
	业务逻辑
}catch(Exception e){
	异常处理
}finally{
	资源关闭
}

try-with-resource的

try(资源打开){
	业务逻辑
}catch(Exception e){
	异常处理
}

案例分析

看了上面的模板,我们的一个直观感觉是省去了finally,并且把资源放在了try后面的括号中。下面展示一段真实的代码。

    public static void main(String[] args) {
        try (
                InputStream fileInputStream = Files.newInputStream(Paths.get("/Users/xie/delete/test.txt"));
                OutputStream outputStream = Files.newOutputStream(Paths.get("/Users/xie/delete/test2.txt"));
        ) {
            byte[] bytes = new byte[1024];
            int len;
            while ((len = fileInputStream.read(bytes)) > 0) {
                outputStream.write(bytes, 0, len);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

注:上面的代码只是演示了一个文件拷贝的方法,如果我们自己写代码请选择1.7提供的Files.copy。

try-with-resource做了什么

我们通过反编译工具来做一下对比

public static void main(String[] args) {
        try {
            InputStream fileInputStream = Files.newInputStream(Paths.get("/Users/xie/delete/test.txt"));
            Throwable var2 = null;
            try {
                OutputStream outputStream = Files.newOutputStream(Paths.get("/Users/xie/delete/test2.txt"));
                Throwable var4 = null;
                try {
                    byte[] bytes = new byte[1024];
                    int len;
                    while((len = fileInputStream.read(bytes)) > 0) {
                        outputStream.write(bytes, 0, len);
                    }
                } catch (Throwable var30) {
                    var4 = var30;
                    throw var30;
                } finally {
                    if (outputStream != null) {
                        if (var4 != null) {
                            try {
                                outputStream.close();
                            } catch (Throwable var29) {
                                var4.addSuppressed(var29);
                            }
                        } else {
                            outputStream.close();
                        }
                    }
                }
            } catch (Throwable var32) {
                var2 = var32;
                throw var32;
            } finally {
                if (fileInputStream != null) {
                    if (var2 != null) {
                        try {
                            fileInputStream.close();
                        } catch (Throwable var28) {
                            var2.addSuppressed(var28);
                        }
                    } else {
                        fileInputStream.close();
                    }
                }
            }
        } catch (IOException var34) {
            var34.printStackTrace();
        }
    }

基本从字节码反编译过来,我们可以看到他是按照我们熟悉的方式编写的代码。所以try-with-resource是语法糖。 这里大家不熟悉的可能就是一场处理里面有addSuppressed的调用。这是一个异常新加的方法,抑制异常。其实这种场景很常见,一个方法里执行出了多个异常,应该报哪个呢,虽然都能表示这个方法调用的失败。以前的做法就是自己catch然后做一些逻辑操作,最后抛出一个。有了这个方法就能实现以前的逻辑,逻辑中也可以取出被抑制的异常信息。

使用的注意事项

  • 第三方库的资源使用需要了解,他的资源是否实现了Closeable。 jdk的资源是都实现了这个接口。
class OutputStream implements Closeable, Flushable {

只有实现了Closeable的才可以和try-with-resource搭配使用。

  • 需要了解每个资源的关闭细节 这里需要列举两个情况。
  1. socket的流的关闭会导致socket关闭。 下面以inputstream为例。SocketInputStream集成FileInputStream,所以也实现了Closeable。
class SocketInputStream extends FileInputStream

但是他的close方法做的事情有点多,会检测socket是否关闭。

    public void close() throws IOException {
        if (closing)
            return;
        closing = true;
        if (socket != null) {
            if (!socket.isClosed())
                socket.close();
        } else
            impl.close();
        closing = false;
    }
  1. 输出流关闭没有flush 这是我调用别人写的库的时候遇到的问题,一直发现文件超过一定大小就会传输丢失。习惯了很多库都是close会顺带帮你做flush。

上面的这些情况,本身就是使用流该注意的地方。

  • 资源声明顺序 通过上面的例子,其实资源也是和我们自己写代码的思路一致,先声明的后关闭。

9版本的改进

java9之前的try-with-resource都是必须做一次赋值的。

    public static void read(InputStream fileInputStream) {
        try (InputStream fileInputStreamTmp = fileInputStream) {
            byte[] bytes = new byte[1024];
            int len;
            while ((len = fileInputStreamTmp.read(bytes)) > 0) {
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

这个问题也挺明显的,我都开始名字上加Tmp来标识了。 9之后就彻底不用这么做了

    public static void read(InputStream fileInputStream) {
        try (fileInputStream) {
            byte[] bytes = new byte[1024];
            int len;
            while ((len = fileInputStream.read(bytes)) > 0) {

            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }