今天java基础教程栏目介绍AutoCloseable接口。

Java 的 AutoCloseable 接口-LMLPHP

本文对 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网其它相关文章!

08-31 06:53