如何优雅地关闭资源

很多时候我们都会用到io资源,比如文件、网络、各种连接等。比如有时候我们需要从一个文本文件中读取数据,一般的步骤是:

  1. 用FileReader打开文件
  2. 包装成BufferReader
  3. 循环地从BufferReader中读取内容,直接读出来的内容为空
  4. 关闭BufferReader和FileReader

用代码实现如下:

FileReader fileReader = null;
BufferedReader bufferedReader = null;
try {
    fileReader = new FileReader("test.txt");
    bufferedReader = new BufferedReader(fileReader);
    String line;
    while ((line = bufferedReader.readLine()) != null) {
        System.out.println(line);
    }
} catch (Exception e) {
    e.printStackTrace();
} finally {
    if (bufferedReader != null) {
        try {
            bufferedReader.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    if (fileReader != null) {
        try {
            fileReader.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

当然做法不止这一种,也可以通过字节流的方式读入,再包装成可以直接读取的方式。但是无论怎么样,我们都需要在finally块中一个个地把各种资源关闭,因为变量作用域的原因,在关闭之前还得判空,于是我们的代码就变成了上面的那样。

太繁琐太不优雅了,你可能会想到,直接把异常一股脑地抛出去不就行了吗?不行,因为调用方只是想读个文件而已,你却要它来处理一大堆异常?而且,就算调用方来处理异常,那么调用方的代码会变得更不优雅,因为还要针对各种不同类型的异常来处理。

下面介绍一种非常优雅的做法,直接上代码:

try (
        FileReader fileReader = new FileReader("test.txt");
        BufferedReader bufferedReader = new BufferedReader(fileReader);
        ) {
    String line;
    while ((line = bufferedReader.readLine()) != null) {
        System.out.println(line);
    }
} catch (IOException e) {
    e.printStackTrace();
}

结构就是

try(){...}catch(...) {...}

和第一种写法最大的区别就是try关键字后多了一对括号。具体用法是,在try后的括号内声明并且定义各种资源,可以是一个也可以是多个,然后别的东西不用变,资源也不用你来关闭了。

可能你会有疑问,fileReader和bufferedReader是真的关闭了吗?首先告诉你,是的。但是怎么验证呢?

我们先关注另一个问题,try后面的括号内是用来定义各种资源的,也就是初始化,是不是所有的东西都可以在里面初始化?比如下面的String line可不可以?

代码编译出错了:需要AutoCloseable类型但是提供了String类型,AutoCloseable是一个接口,最早出现在jdk1.7中。直接贴一段该接口的说明

An object that may hold resources (such as file or socket handles)
until it is closed. The {@link #close()} method of an {@code AutoCloseable}
object is called automatically when exiting a {@code
try}-with-resources block for which the object has been declared in
the resource specification header. This construction ensures prompt
release, avoiding resource exhaustion exceptions and errors that
may otherwise occur.

最核心的一点就是当退出try块,这些资源会自动调用close方法。这样就达到了自动关闭资源的目的,如果去看FileReader和BufferedReader的源码,它们都间接实现了AutoCloseable接口。

为了验证这一点,我们自己写一个实现AutoCloseable的类:

public class Auto implements AutoCloseable {

    public void run() {
        System.out.println("run");
    }

    @Override
    public void close() throws Exception {
        System.out.println("close");
    }
}

然后在测试类中生成并且调用run方法

public class Main {

    public static void main(String[] args) {
        try (Auto auto = new Auto()){
            auto.run();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}

运行后的结果如下:

run
close

上面的测试类并没有显式调用close方法,但是最后这个方法被调用了。到这里我们可以猜测是在编译的时候,javac命令已经帮我们把调用close方法的指令自动生成了,我们用javap命令反编译来看一下:

C:\>javap -c Main.class
Compiled from "Main.java"
public class source.Main {
  public source.Main();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public static void main(java.lang.String[]);
    Code:
       0: new           #2                  // class source/Auto
       3: dup
       4: invokespecial #3                  // Method source/Auto."<init>":()V
       7: astore_1
       8: aconst_null
       9: astore_2
      10: aload_1
      11: invokevirtual #4                  // Method source/Auto.run:()V
      14: aload_1
      15: ifnull        85
      18: aload_2
      19: ifnull        38
      22: aload_1
      23: invokevirtual #5                  // Method source/Auto.close:()V
      26: goto          85
      29: astore_3
      30: aload_2
      31: aload_3
      32: invokevirtual #7                  // Method java/lang/Throwable.addSuppressed:(Ljava/lang/Throwable;)V
      35: goto          85
      38: aload_1
      39: invokevirtual #5                  // Method source/Auto.close:()V
      42: goto          85
      45: astore_3
      46: aload_3
      47: astore_2
      48: aload_3
      49: athrow
      50: astore        4
      52: aload_1
      53: ifnull        82
      56: aload_2
      57: ifnull        78
      60: aload_1
      61: invokevirtual #5                  // Method source/Auto.close:()V
      64: goto          82
      67: astore        5
      69: aload_2
      70: aload         5
      72: invokevirtual #7                  // Method java/lang/Throwable.addSuppressed:(Ljava/lang/Throwable;)V
      75: goto          82
      78: aload_1
      79: invokevirtual #5                  // Method source/Auto.close:()V
      82: aload         4
      84: athrow
      85: goto          93
      88: astore_1
      89: aload_1
      90: invokevirtual #9                  // Method java/lang/Exception.printStackTrace:()V
      93: return
    Exception table:
       from    to  target type
          22    26    29   Class java/lang/Throwable
          10    14    45   Class java/lang/Throwable
          10    14    50   any
          60    64    67   Class java/lang/Throwable
          45    52    50   any
           0    85    88   Class java/lang/Exception
}

果不其然,在run方法后面调用了Auto.close方法。这里值得说的是出现了多个close方法的调用,原因就是,这里的处理其实相当于在一个finally块中调用close方法,为了保证finally中的语句会执行,会在抛出异常的后面再执行finally中的语句,也就是close方法,最后才return。

posted @ 2020-06-15 11:03  我是满意吖  阅读(555)  评论(0编辑  收藏  举报