转载

java优雅的关闭资源 : try-with-resources (effect java 学习笔记 9)

客官,往这瞅

java优雅的关闭资源 : try-with-resources (effect java 学习笔记 9)

背景

在我们日常的开发过程中,会调用需要手动close的资源。比如InputStream, OutputStream ,java.sql.Connection,socket等。别想着java有了GC,GC大大说,不是我家的,谁爱用谁管。得嘞,我们自己管。

没有对比就没有伤害,我们来伤害吧!

对比伤害

一、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();
            }
        }
        return "";
    }
复制代码

这样看起来并不坏,但是当添加第二个资源时,情况就会变得更糟:

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[1024];
                int n;
                while ((n = in.read(buf)) >= 0) {
                    out.write(buf, 0, n);
                }
            } finally {
                out.close();
            }
        } finally {
            in.close();
        }
        
    }
复制代码

即使是用 try-finally 语句关闭资源的正确代码,如前面两个代码示例所示,也有一个微妙的缺陷。 try-with- resources 块和 finally 块中的代码都可以抛出异常。 例如,在 firstLineOfFile 方法中,由于底层物理设备发生 故障,对 readLine 方法的调用可能会引发异常,并且由于相同的原因,调用 close 方法可能会失败。 在这种 情况下,第二个异常完全冲掉了第一个异常。 在异常堆栈跟踪中没有第一个异常的记录,这可能使实际系统中的调 试非常复杂——通常这是你想要诊断问题的第一个异常。 虽然可以编写代码来抑制第二个异常,但是实际上没有人这 样做,因为它太冗长了。

二、try-with-resources

当java 7 引入了try-with-resources语句后,这些就都解决了。要使用这个语法糖,资源必须都实现AutoCloseAble接口.Java 类库和第三方库中的许多类和接口现在都实现了或继承了AutoCloseable接口。如果自己编写的类表示必须关闭的资源,那么也应该实现AutoCloseable接口。

public interface AutoCloseable {
    void close() throws Exception;
}
复制代码

修改后的实例如下:

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[1024];
            int n;
            while ((n = in.read(buf)) >= 0)
                out.write(buf, 0, n);
        }
    }
复制代码

不仅 try-with-resources 版本比原始版本更精简,更好的可读性,而且它们提供了更好的诊断。 考虑 firstLineOfFile 方法。 如果调用 readLine 和(不可见) close 方法都抛出异常,则后一个异常将被抑制 (suppressed),而不是前者。 事实上,为了保留你真正想看到的异常,可能会抑制多个异常。 这些抑制的异常没 有被抛弃, 而是打印在堆栈跟踪中,并标注为被抑制了。 你也可以使用 getSuppressed 方法以编程方式访问它 们,该方法在 Java 7 中已添加到的 Throwable 中。

是不是很神奇,来让我们看看原理吧

原理:

下面是第二个实例编译后的代码,看了是不是恍然大悟,原来一切都是编译器帮我们做好了

static void copy(String src, String dst) throws IOException {
        InputStream in = new FileInputStream(src);
        Throwable var3 = null;

        try {
            OutputStream out = new FileOutputStream(dst);
            Throwable var5 = null;

            try {
                byte[] buf = new byte[1024];

                int n;
                while((n = in.read(buf)) >= 0) {
                    out.write(buf, 0, n);
                }
            } catch (Throwable var29) {
                var5 = var29;
                throw var29;
            } finally {
                if (out != null) {
                    if (var5 != null) {
                        try {
                            out.close();
                        } catch (Throwable var28) {
                            var5.addSuppressed(var28);
                        }
                    } else {
                        out.close();
                    }
                }

            }
        } catch (Throwable var31) {
            var3 = var31;
            throw var31;
        } finally {
            if (in != null) {
                if (var3 != null) {
                    try {
                        in.close();
                    } catch (Throwable var27) {
                        var3.addSuppressed(var27);
                    }
                } else {
                    in.close();
                }
            }

        }

    }
复制代码

使用细节:

相信细心的看官都发现了,实例的资源都是声明在括号中,并且实例化的吧。来我们继续说说怎么回事, java 9以前:

static String readData(String message) throws IOException {

      Reader inputString = new StringReader(message);
      BufferedReader br = new BufferedReader(inputString);
      try (BufferedReader br1 = br) {
         return br1.readLine();
      }
   }
复制代码

如果不这样做,br资源不能释放,这个我们需要注意,但是在java 9以后,就不用额外声明变量了

static String readData(String message) throws IOException {

      Reader inputString = new StringReader(message);
      BufferedReader br = new BufferedReader(inputString);
      try (br) {
         return br.readLine();
      }
   }
复制代码

总结:

结论很明确:在处理必须关闭的资源时,使用 try-with-resources 语句替代 try-finally 语句。 生成的代码更简洁, 更清晰,并且生成的异常更有用。 try-with-resources 语句在编写必须关闭资源的代码时会更容易,也不会出错,而 使用 try-finally 语句实际上是不可能的。

java优雅的关闭资源 : try-with-resources (effect java 学习笔记 9)

)

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