探究 try-with-resources语法

QuibblerQuibbler 2021-07-01 约 11 分钟 1894 次阅读

探究 try-with-resources语法

有没有想过 try-with-resources 语法到底是在虚拟机层面做了处理还是在编译时候做了手脚?得空刨根问底研究一下。

1、可关闭对象

开发中用到很多表示资源的可关闭对象:FileOutputStream、Socket等等。

1.1、AutoCloseable接口

这些表示资源的对象通常继承自Closeable接口,用完需要关闭。其close()方法中执行了释放资源的操作,抛出的是IOException。

public interface Closeable extends AutoCloseable {
        void close() throws IOException;
    }

Closeable接口则继承自更抽象的AutoCloseable接口,这里的close()方法抛出通用Exception。

public interface AutoCloseable {
        void close() throws Exception;
    }

继承AutoCloseable接口,写一个资源类Resource,为了演示方便都是空实现方法。

public static class Resource implements AutoCloseable {
    
        public void read() throws IOException {
            //TODO
        }
        
        @Override
        public void close() throws Exception {
            //TODO close resource
        }
        
    }

1.2、传统写法

先看一下传统写法,创建或者获取到资源对象,使用完毕需要在finally块中关闭资源对象。

Resource resource = new Resource();
    try {
        resource.read();
    } catch (Exception e) {
        //handle read exception
    } finally {
        try {
            resource.close();
        } catch (Exception e) {
            //catch exception while close
        }
    }

资源关闭的时候还需要进行异常捕获,可以抽取出通用的资源关闭工具方法,这样能够减少重复代码。

public static void close(Closeable resource) {
        try {
            if (resource != null) {
                resource.close();
            }
        } catch (Exception e) {
            //TODO
        }
    }

1.3、try-with-resources写法

使用Java的try-with-resources语法,能够极大的简化上面的代码。

try (Resource resource = new Resource()) {
            resource.read();
        } catch (Exception e) {
            //handle read exception
        }

那么这个语法到底是怎么工作的呢?说不定这个问题能问倒一片。

2、探究

try-with-resources是 JDK 7 中增加异常处理机制,可以很方便地关闭在 try-catch 语句块中使用的资源。那么这是语法糖还是虚拟机层面做了处理呢?

javac查看编译后的Java代码.class文件,可以看到编译后生成的代码。前面用try-with-resources写的简短几行代码:

try (Resource resource = new Resource()) {
            resource.read();
        } catch (Exception e) {
            //handle read exception
        }

通过编译生成的代码片段如下。果然,是在编译的时候做了手脚,由编译器自动生成资源关闭的代码。

try {
        Resource resource = new Resource();
        try {
            resource.read();
        } catch (Throwable var5) {
            try {
                resource.close();         //关闭一: 读取异常
            } catch (Throwable var4) {
                var5.addSuppressed(var4);
            }
            throw var5;
        }
        resource.close();      //关闭二: 读取资源正常
    } catch (Exception var6) {
    }

可以发现,Resource对象的创建在try-catch范围内。有两处地方进行对象的关闭:

①读取出现异常,在内部的try-catch异常捕获处执行对象的关闭操作。如果对象关闭出错,还会用addSuppressed()记录异常间的关系。

②读取顺利进行,在外部的try-catch最后执行资源关闭操作,如果对象关闭发生异常,将会被外部的try-catch捕获。

2.1、简化理解

简化一下try-with-resources代码,只创建资源对象,不对资源对象进行任何操作:

try (Resource resource = new Resource()) {
        //TODO
    } catch (Exception e) {
        //handle exception
    }

这时编译生成的.class代码就少一些了,很容易看出来,编译器将对象创建放在try-catch内,并且添加了资源关闭代码:

try {
        Explore.Resource resource = new Explore.Resource();
        
        resource.close();
    } catch (Exception var2) {
    }

代码逻辑流程是:创建对象 -> 进行操作 -> 关闭资源。

2.2、对象关闭顺序

到这里还没有结束,继续探索下去。如果在try-with-resources中创建了多个对象,它们是如何处理的呢?不妨再写一个代码段:

try (Resource one = new Resource();Resource two = new Resource();Resource three = new Resource()) {
        //TODO
    } catch (Exception e) {
        //handle exception
    }

编译生成的代码看着有点复杂,层层嵌套的try-catch眼花缭乱:

try {
        Explore.Resource one = new Explore.Resource();
        try {
            Explore.Resource two = new Explore.Resource();
            try {
                Explore.Resource three = new Explore.Resource();
                three.close();
            } catch (Throwable var7) {
                try {
                    two.close();
                } catch (Throwable var6) {
                    var7.addSuppressed(var6);
                }
                throw var7;
            }
            two.close();
        } catch (Throwable var8) {
            try {
                one.close();
            } catch (Throwable var5) {
                var8.addSuppressed(var5);
            }
            throw var8;
        }
        one.close();
    } catch (Exception var9) {
    }

不过很容易看出来,对象创建的顺序是:one、two、three,而关闭的顺序恰好相反:three、two、one。和栈一样后进先出。

原来try-with-resources语法糖是这样工作的,豁然开朗。

相关推荐

精选
获取系统SDK版本、判断手机ROM
Code

获取系统SDK版本、判断手机ROM

Build获取系统SDK版本Android中部分API的使用,需要在特定的SDK版本之后才能使用,因此在兼容老版本SDK的时候,经常需要判断API的版本。各种Android版本的对应关系参考《Android各版本对应的SDK版本》判断手机ROM有时候需要判断手机系统的ROM,检测ROM是MIUI、EMUI还是Flyme,可以使用getprop命令,去系统build.prop文件(关于build.p

3.7k
精选
AndroidStudio中各种中文乱码问题
Code

AndroidStudio中各种中文乱码问题

AndroidStudio中各种中文乱码问题1、编译Java错误信息乱码在出现这个Annotation processors must be explicitly declared now...问题的时候,正好也发现AndroidStudio Build Output错误信息都乱码。经常遇到各种问题,习惯了,现在遇到的问题,都是以后的答案。 1.1、修改项目build.gradle(无效)在整个p

2.4k
AndroidStudio 报错:has no declaration in the base values folder
Code

AndroidStudio 报错:has no declaration in the base values folder

在资源文件中正常定义的值,也能编译运行。昨天还正常,今天一打开就报错。应该又是AndroidStudio自身的Bug了。原因众说纷纭,参考Stack Overflow 一篇类似的讨论。解决:File => Invalidate Caches / Restart => Invalidate and Restart.

4.4k