try-with-resource:关闭资源

686 阅读2分钟

最近idea集成了SonarLint插件,代码检测会有以下截图问题:

传统关闭资源方式(try-finally)

为了确保外部资源一定要被关闭,通常关闭代码被写入finally代码块中:

public static void main(String[] args) {
    FileInputStream fileInputStream = null;
    try {
        fileInputStream = new FileInputStream(new File("C:\\test.txt"));
        System.out.println(fileInputStream.read());
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        if (fileInputStream != null) {
            try {
                fileInputStream.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

这种方式关闭资源时,要考虑到关闭IO流时可能会出现的异常问题;特别是IO对象比较多时,使用finally关闭资源,可能会出现finally嵌套,代码不简洁。

try-with-resource方式

在JDK7中新增了try-with-resource语法:当一个外部资源的句柄对象(比如FileInputStream对象)实现了AutoCloseable接口,那么就可以将上面的板式代码简化为如下形式:

public static void main(String[] args) {
    try (FileInputStream fileInputStream = new FileInputStream(new File("C:\\test.txt"))) {
        System.out.println(fileInputStream.read());
    } catch (IOException e) {
        e.printStackTrace();
    }
}

对比传统关闭资源方式,此种写法代码非常简洁。

实现原理

try-with-resource并不是JVM虚拟机的新增功能,只是JDK实现了一个语法糖, 编译器会自动帮生成finally块,并且在里面调用了资源的close方法。
可以反编译class文件查看:
【注:有些反编译工具反编译后的内容和java源代码一致,比如jd-gui;idea自带的反编译插件java-decompiler则正常】

public static void main(String[] args) {
        try {
            FileInputStream fileInputStream = new FileInputStream(new File("C:\\test.txt"));
            Throwable var2 = null;

            try {
                System.out.println(fileInputStream.read());
            } catch (Throwable var12) {
                var2 = var12;
                throw var12;
            } finally {
                if (fileInputStream != null) {
                    if (var2 != null) {
                        try {
                            fileInputStream.close();
                        } catch (Throwable var11) {
                            var2.addSuppressed(var11);
                        }
                    } else {
                        fileInputStream.close();
                    }
                }

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

    }

异常屏蔽

在上述反编译代码中有一行:

var2.addSuppressed(var11);

从Java 1.7开始,Throwable类新增了addSuppressed方法:当对外部资源进行处理时【System.out.println(fileInputStream.read())】,如果遭遇了异常,且在随后的关闭外部资源过程中【fileInputStream.close()】,又遭遇了异常,那么你catch到的将会是对外部资源进行处理时遭遇的异常,关闭资源时遭遇的异常将被“抑制”但不是丢弃,通过异常的getSuppressed方法,可以提取出被屏蔽的异常。