今天java基础教程栏目介绍AutoCloseable接口。
本文对 try-with-resources 语法进行了较为深入的剖析,验证了其为一种语法糖,同时给出了其实际的实现方式的反编译结果,相信你在看完本文后,关于 AutoCloseable 的使用你会有新的收获。
一、前言
最近用到了 JDK 7 中的新特性 try-with-resources 语法,感觉到代码相对简洁了很多,于是花了点时间详细学习了下,下面分享给大家我的学习成果。
二、简单了解并使用
try-with-resources语法比较容易使用,一般随便搜索看下示例代码就能用起来了。JDK 对这个语法的支持是为了更好的管理资源,准确说是资源的释放。
当一个资源类实现了该接口close方法,在使用try-with-resources语法创建的资源抛出异常后,JVM会自动调用close 方法进行资源释放;当没有抛出异常正常退出try代码块时也会自动调用close方法。像数据库链接类Connection,io类 InputStream 或 OutputStream 都直接或者间接实现了该接口。
下面我们通过代码示例来了解如何使用,首先创建一个实现了AutoCloseable接口的类:
public class Resource implements AutoCloseable{ public void read() { System.out.println("do something"); } @Override public void close() throws Exception { System.out.println("closed"); } }复制代码
1、 不使用这个语法,须主动close关闭
public static void f1(){ Resource resource = new Resource(); try { resource.read(); } finally{ try { resource.close(); } catch (Exception e) { } } }复制代码
2、使用这个语法,代码更加优雅简洁
public static void f2(){ try(Resource resource = new Resource()) { resource.read(); } catch (Exception e) { } }复制代码
注意:read方法本身不抛异常,但是编码时idea提示必须要有catch块,因此可知这个异常的捕获是捕获的close方法抛出的异常。
3、 改成Closeable接口,也可以
接着我们将Resource类上的AutoCloseable接口改为Closeable(如下),此时需要将close方法的异常签名改成IOException,否则编译不通过。注意下面代码主动抛出IOException目的为满足异常签名,否则idea提示要删掉异常签名,变成无异常签名。因此在实现Closeable接口后,异常签名要么没有,要么是IOException或者其子类。
public class Resource implements Closeable{ public void read() { System.out.println("do something"); } @Override public void close() throws IOException{ System.out.println("closed"); throw new IOException(); } }复制代码
接着我们看下Closeable和AutoCloseable关系,发现除了继承关系,Closeable只是将close方法的异常签名变得稍微具体了,从Exception变为IOException。
public interface AutoCloseable { //省略Java doc void close() throws Exception; }复制代码
public interface Closeable extends AutoCloseable { ////省略Java doc public void close() throws IOException; }复制代码
因此无论是实现了 JDK 中的java.lang.AutoCloseable还是java.io.Closeable接口,都能使用try-with-resources语法。此处注意还有点不同的是两个接口的包路径的差异。
三、Java doc 阅读
1、 AutoCloseable 中的 Java doc
以上是 AutoCloseable 的完整 Java doc,大概意思是:
接着我们看下AutoCloseable.close方法上的Java doc,以下是原文:
翻译如下:
2、 Closeable 中的 Java doc
Closeable类上的Java doc无额外有用信息,我们看下Closeable.close方法上的Java doc:
翻译如下:
四、揭开魔术面纱
1、怎么实现的
由于不确定try-with-resources是一种语法糖,还是在JVM虚拟机层面新加指令进行的支持,我尝试使用javap解析class文件后,通过解析结果查表对照发现,两段代码不同之处对应的字节码指令并不是此语法的含义,判定其确实是一种语法糖,在变成class文件的时候已经由编译器(javac)处理了。既然如此,我们就直接将其反编译,得到的结果如下:
public static void f1() { Resource resource = new Resource(); try { resource.read(); } finally { try { resource.close(); } catch (Exception var7) { } } }复制代码
public static void f2() { try { Resource resource = new Resource(); Throwable var1 = null; try { resource.read(); } catch (Throwable var11) { var1 = var11; throw var11; } finally { if (resource != null) { if (var1 != null) { try { resource.close(); } catch (Throwable var10) { var1.addSuppressed(var10); } } else { resource.close(); } } } } catch (Exception var13) { } }复制代码
可以看到代码片段1与原代码几乎一样,但是代码片段2与原代码确实大相径庭,接着我们来分析下:
**关注点1:**new Resource()操作被放到了try内部了,如果不用try-with-resources语法,我们一般放到try外面。
**关注点2:**resource.read()操作被捕获了Throwable,并通过赋值记录了下来。
**关注点3:**fianally中的if (resource != null)这一步骤是多余的,因为只有在new Resource()操作抛异常才会存在resource为空的情况,然而这个时候就不会执行到这里来了。
**关注点4:**此时一定要执行resource.close()了,当var1不为空(即resource.read()抛出了异常),则需捕获close可能抛出的异常,并调用var1.addSuppressed记录关联try中抛出的异常,我们后面再分析这步骤。
**关注点5:**由于在原始代码里我们捕获了close方法抛出的异常,因此这里当上一步的var1为空时可能抛出的异常需要在最外层捕获。
2、Throwable.addSuppressed
public final synchronized void addSuppressed(Throwable exception) { if (exception == this) throw new IllegalArgumentException(SELF_SUPPRESSION_MESSAGE, exception); if (exception == null) throw new NullPointerException(NULL_CAUSE_MESSAGE); if (suppressedExceptions == null) // Suppressed exceptions not recorded return; if (suppressedExceptions == SUPPRESSED_SENTINEL) suppressedExceptions = new ArrayList<>(1); suppressedExceptions.add(exception); }复制代码
这段代码我们只需要了解,每一个Throwable实例中有一个List类型的字段,本方法将入参中的异常添加到了这个List的末尾。
翻译如下:
阅读完这个Java doc注释后,建议结合Throwable类源码加深理解。
五、JDK 9中的改进
通过搜索学习,还了解到在 JDK 9中对此语法进行了改进,JSR334对其进行了描述,感兴趣的可以点击 此处 或者 此处 来进一步了解优化点。
六、最佳实践
本次学习总结出如下最佳实践:
- 使用try-with-resources语法无论是否抛出异常在try-block执行完毕后都会调用资源的close方法。
- 使用try-with-resources语法创建多个资源,try-block执行完毕后调用的close方法的顺序与创建资源顺序相反。
- 使用try-with-resources语法,try-block块抛出异常后先执行所有资源(try的()中声明的)的close方法然后在执行catch里面的代码然后才是finally。
- try的()中管理的资源在构造时抛出的异常,需要在本try对应的catch中捕获。
- 自动调用的close方法显示声明的异常,需要在本try对应的catch中捕获。
- 当你的try-catch-finally分别在try/catch/finally中有异常抛出,而无法抉择抛出哪个异常时,你应该抛出try中对应的异常,并通过Throwable.addSuppressed方式记录它们间的关系。
以上就是Java 的 AutoCloseable 接口的详细内容,更多请关注Work网其它相关文章!