面试官:“来,谈谈try-with-resource写法” |8月更文挑战

·  阅读 677
面试官:“来,谈谈try-with-resource写法” |8月更文挑战

前言

try-with-resouce写法是在JDK1.7引入的一个语法糖,用来进行io数据流关闭的简易写法。

常见的Java关闭IO数据流的写法

在JDK 1.7之前,我们先看一下常见的写法

String readTextFromFile(String path) {
        BufferedReader br = null;
        StringBuilder sb = new StringBuilder();
        try {
            br = new BufferedReader(new InputStreamReader(new FileInputStream(path)));
            String line = null;
            while ((line = br.readLine()) != null) {
                sb.append(line).append("\n");
            }
            br.close();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (br != null) {
                try {
                    br.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return sb.toString();
    }
复制代码

看readTextFromFile这个方法,从一个文件中用BufferedReader读取数据内容并返回一个字符串,这是一个非常传统与标准的io读取与关闭的写法,利用finally机制,因为finally一定会被执行,所以可以保证资源一定被释放,同时因为close方法会抛出一个异常,所以要在finally里添加try-catch用来处理这个异常,整体来看十分臃肿和繁琐。

try-with-resources写法

在 JDK 1.7开始,为我们提供了以中便捷的关闭io流的方法

String readTextFromFile(String path){
        StringBuilder sb=new StringBuilder();
        try(BufferedReader br=new BufferedReader(new InputStreamReader(new FileInputStream(path)))){
            String line=null;
            while((line=br.readLine())!=null){
                sb.append(line).append("\n");
            }
            return sb.toString();
        }
        catch(IOException e){
            e.printStackTrace();
        }
        return sb.toString();
    }
复制代码

可以看到,没有了finally,没有了close调用,感觉代码缩短了一半,而这些只是需要你在实例化BufferedReader的时候放在try中

try(BufferedReader br=new BufferedReader(new InputStreamReader(new FileInputStream(path)))){

}

原理

我们看一下使用try-with-resources的写法编译后的字节码再反编译之后的样子

String readTextFromFile(String path) {
    StringBuilder sb = new StringBuilder();
​
    try {
      BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(path)));
​
      String var5;
      try {
        String line = null;
​
        while(true) {
          if ((line = br.readLine()) == null) {
            var5 = sb.toString();
            break;
          }
​
          sb.append(line).append("\n");
        }
      } catch (Throwable var7) {
        try {
          br.close();
        } catch (Throwable var6) {
          var7.addSuppressed(var6);
        }
​
        throw var7;
      }
​
      br.close();
      return var5;
    } catch (IOException var8) {
      var8.printStackTrace();
      return sb.toString();
    }
  }
复制代码

可以看到这个跟我们标准的Java io流关闭的写法还是差不多的,只不过没有使用finally机制,而是在各种catch中进行close方法的调用,可以看到try-with-resouce其实就是一个语法糖,由编译器在编译为字节码的时候为你自动添加了close方法调用,使你更关注于逻辑,也使代码显得更加整洁。那么既然是语法糖,我们自己能不能利用这个语法特性呢?先看一下Reader的源码,

截屏2021-08-05 上午11.37.02.png

可以看到Reader实现了Closeable接口,这个接口有一个close方法,也即是我们调用的BufferedReader的close方法,再看Closeable接口

截屏2021-08-05 上午11.37.08.png

可以看到Closeable接口继承自AutoCloseble接口,自动可关闭对象?是不是有那个味道了?进入这个接口,可以看到这个类的注释

/**

  • 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.

  • @apiNote

  • It is possible, and in fact common, for a base class to

  • implement AutoCloseable even though not all of its subclasses or

  • instances will hold releasable resources. For code that must operate

  • in complete generality, or when it is known that the {@code AutoCloseable}

  • instance requires resource release, it is recommended to use {@code

  • try}-with-resources constructions. However, when using facilities such as

  • {@link java.util.stream.Stream} that support both I/O-based and

  • non-I/O-based forms, {@code try}-with-resources blocks are in

  • general unnecessary when using non-I/O-based forms.

  • @author Josh Bloch

  • @since 1.7

*/

可以看到,注释里明确说明,如果实现了这个接口,那么就可以使用try-with-resource这种语法用来自动关闭。这里其实有个小问题,就是Closeable接口继承自AutoCloseabl,同时在Cloaseable中也声明了close方法,但是会抛出一个IOException,不同于AutoCloseable中close方法抛出的Exception,刚开始不太明白这种写法的含义,目前来看通过这种方式缩减了所有IO操作关注的异常范围,只关注IOException,避免异常范围过大导致的异常指向不明确。

通过实现AutoCloseable接口,我们就可以享受try-with-resources带来的语法便利。我们定义一个AutoCloseClass类来验证一下

​
   public class Test {
​
    public static void main(String[] args) {
        try(AutoCloseClass d=new AutoCloseClass()){
            System.out.println("hello world");
        }
        catch(Exception e){
            e.printStackTrace();
        }
    }
    
     public static class AutoCloseClass implements AutoCloseable{
​
        @Override
        public void close() throws IOException {
           System.out.println("close honey");
            
        }
    }
    
   }
复制代码

可以看到先打印 hello world,然后打印close honey

kotlin写法

现如今Kotlin已经作为Android开发的第一语言,那么在Kotlin中有没有类似的语法呢?实际上也是有的,有一个内联的扩展方法,use,它就是来做相同的事情的。

public inline fun <T : Closeable?, R> T.use(block: (T) -> R): R {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    var exception: Throwable? = null
    try {
        return block(this)
    } catch (e: Throwable) {
        exception = e
        throw e
    } finally {
        when {
            apiVersionIsAtLeast(1, 1, 0) -> this.closeFinally(exception)
            this == null -> {}
            exception == null -> close()
            else ->
                try {
                    close()
                } catch (closeException: Throwable) {
                    // cause.addSuppressed(closeException) // ignored here
                }
        }
    }
}
​
复制代码

可以看到use是一个内联的泛型扩展函数,接收者为一个实现Closeable的接口类型T,传入一个函数类型,接收泛型参数T,然后在finally中调用close方法进行关闭。

总结

try-with-resources是一个很有用的语法糖,而且在《Effective Java》中也被推荐使用来进行io流的关闭。

关注我的公众号”滑板上的老砒霜"

分类:
Android
标签: