try-with-resource 语法

发布时间 2023-03-26 19:43:17作者: 忘路之远近

新语法

在java7之前,释放资源的一般写法如下

public String readFirstLine(String path) throws IOException {
    FileReader fr = null;
    BufferedReader br = null;
    try {
        fr = new FileReader(path);
        br = new BufferedReader(fr);
        return br.readLine();
    } finally {
        if (br != null) {
            br.close();
        }
        if (fr != null) {
            fr.close();
        }
    }
}

java7引入了try-with-resource语法,上面的逻辑可改写为

public String readFirstLine(String path) throws IOException {
    try (FileReader fr = new FileReader(path); 
          BufferedReader br = new BufferedReader(fr)) {
        return br.readLine();
    }
}

新语法需要资源类实现Closeable或AutoCloseable接口。

Java做出这种语法优化的原因

  1. 旧写法br.close()方法执行抛出异常时,依然存在资源泄露的可能
  2. 旧写法try块和finally块均抛出异常时,try块的异常被覆盖,抛出去的是finally块的异常
  3. 比较之下,新写法比旧写法更简洁,可读性更好

举例说明,比如资源类A与资源类B如下:

public class A implements AutoCloseable {

    public String readLine() {
        throw new UnsupportedOperationException("Failed to read A");
    }

    @Override
    public void close() {
        throw new RuntimeException("Failed to close A");
    }

}

@AllArgsConstructor
public class B implements AutoCloseable {
    private A a;

    public String readLine() {
        return a.readLine();
    }

    @Override
    public void close() {
        throw new RuntimeException("Failed to close B");
    }

}

旧写法下资源读取及关闭:

public class TryWithResource {

    public String readFirstLineOldWay() {
        A a = null;
        B b = null;
        try {
            a = new A();
            b = new B(a);
            return b.readLine();
        } finally {
            if (a != null) {
                a.close();
            }
            if (b != null) {
                b.close();
            }
        }
    }

    public static void main(String[] args) {
        new TryWithResource().readFirstLineOldWay();
    }

}

旧写法执行结果为:

Exception in thread "main" java.lang.RuntimeException: Failed to close A
	at com.czy.demo.A.close(A.java:11)
	at com.czy.demo.TryWithResource.readFirstLineOldWay(TryWithResource.java:21)
	at com.czy.demo.TryWithResource.main(TryWithResource.java:31)

可见:

  1. a.close() 抛异常,b.close() 未执行,b 存在资源泄露的可能
  2. 抛出异常为finally块的RuntimeException,try块中的UnsupportedOperationException被覆盖

再看下新写法下资源读取及关闭:

public class TryWithResource {

    public String readFirstLineNewWay() {
        try (A a = new A();
             B b = new B(a)) {
            return b.readLine();
        }
    }

    public static void main(String[] args) {
        new TryWithResource().readFirstLineNewWay();
    }

}

新写法执行结果为:

Exception in thread "main" java.lang.UnsupportedOperationException: Failed to read A
	at com.czy.demo.A.readLine(A.java:6)
	at com.czy.demo.B.readLine(B.java:10)
	at com.czy.demo.TryWithResource.readFirstLineNewWay(TryWithResource.java:8)
	at com.czy.demo.TryWithResource.main(TryWithResource.java:30)
	Suppressed: java.lang.RuntimeException: Failed to close B
		at com.czy.demo.B.close(B.java:15)
		at com.czy.demo.TryWithResource.readFirstLineNewWay(TryWithResource.java:9)
		... 1 more
	Suppressed: java.lang.RuntimeException: Failed to close A
		at com.czy.demo.A.close(A.java:11)
		at com.czy.demo.TryWithResource.readFirstLineNewWay(TryWithResource.java:9)
		... 1 more
  1. b.close()与a.close()均执行(按创建逆序执行)
  2. 抛出异常为try块的UnsupportedOperationException,finally块的异常以Suppressed Exception的方式保存在抛出异常中(Throwable#getSuppressed获取)。

try-with-resoure写法要注意的一个小点

资源A与资源B如下:

public class A implements AutoCloseable {

    public String readLine() {
        return "A";
    }

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

}

@AllArgsConstructor
public class B implements AutoCloseable {
    private A a;

    public String readLine() {
        return a.readLine();
    }

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

}

通过console输出可以清楚地看到下面两种方法关闭资源的方式不同:

/**
  * console输出为:
  * B
  * A
  */
public String readFirstLineNewWay() {
    try (A a = new A();
          B b = new B(a)) {
        return b.readLine();
    }
}

/**
  * console输出为:
  * B
  */
public String readFirstLineWrongWay() {
    try (B b = new B(new A())) {
        return b.readLine();
    }
}

就是说,被try-with-resource自动执行close方法关闭资源,需要资源类的创建语句在try中依次声明,分号";"来分隔。
不过对于像BufferedReader这种java.io的资源类,close()方法的实现里都会执行源资源类的close()方法,所以文章开头的写法是没问题的。

总结

  • 使用新语法关闭资源
  • 需要自动执行close()方法的资源类依次声明在try块中,就不用考虑方法的实现了