前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >不会吧,还有人在 finally 里关闭资源?

不会吧,还有人在 finally 里关闭资源?

作者头像
Java程序猿阿谷
发布2020-11-12 10:19:12
5590
发布2020-11-12 10:19:12
举报
文章被收录于专栏:Java快速进阶通道

日常标题党吸引读者,本篇文章主讲 try-with-resource 语法糖。

应用场景

身为 Java 大神的各位肯定避免不了使用各式各样需要关闭的 Stream 或 Client 吧。例如 FileInputStream、HTTPClient 之类的。那么大家肯定会遇到以下恶心的代码

代码语言:javascript
复制
        File file = new File("/root/usr/file.txt");
        FileInputStream fileInputStream = null;
        try {
            fileInputStream = new FileInputStream(file);
            System.out.println("fileInputStream = " + fileInputStream);
        } catch (FileNotFoundException e) {
            log.error("error", e);
        } finally {
            if (null != fileInputStream) {
                try {
                    fileInputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

此处使用 System.out.println("fileInputStream = " + fileInputStream) 来代替模拟的业务操作,在咱们这种代码洁癖的人眼中,这个 finally 简直是毒瘤!那么怎么把这坨恶心又难看的代码给优化掉呢?其实 Java 已经帮我们想好了。

代码改造

那么究竟是怎么操作的呢,这就要引入我们今天的重点:try-with-resource

我们先看效果,再说原理。针对上述一大坨代码,可以改为如下一般简洁的几行。这样可以让程序员将更多的心思放在业务逻辑上,而不是异常处理和资源关闭:

代码语言:javascript
复制
        File file = new File("/root/usr/file.txt");
        try (FileInputStream fileInputStream = new FileInputStream(file)) {
            System.out.println("fileInputStream = " + fileInputStream);
        } catch (IOException e) {
            log.error("error", e);
        }

那么读者可能会问了,我们不用去管关闭资源,那这些事情谁来干了?毫无疑问当然是虚拟机!我们跟入 FileInputStream 这个类并查看他的父类 InputStream 会发现,这个类实现了一个名为 Closeable 接口,而这个接口又是继承了 java.lang 包中的 AutoCloseable 接口。这个接口只有一个方法,那就是 close 方法。InputStream 中实现了这个方法,但是方法体是空的,真正的执行者是他的子类 FileInputStream ,FileInputStream 中重写了这个 close 方法来实现资源的关闭。

有的读者朋友跟到这里可能已经蒙了,说一千道一万,继承这个实现那个,那到最后到底谁关的。或者说,到底是谁执行的呢?那么大家请跟我进入源码时刻!

实现原理

说是源码其实有点惭愧,这里没啥源码可讲。只不过是虚拟机在编译的时候给他把 finally 加上了而已。我们来看下反编译之后的类:

image

是不是有人开始砸桌子大呼上当了?是的,这里根本没啥蹊跷的,就是 JVM 把你不想写的代码给你添上了而已。至于他是怎么改的,各位大佬请自行翻阅 JVM 源码,小弟这里帮不上了。

题外话

基本看到这里的都是真爱了,其实上面那些东西网上都讲烂了。那我就多几句嘴,说上几个关于 try-with-resource 的小技巧和有意思的东西吧。

1、IDEA 自动生成

如果有细心的朋友,其实会发现有的IDE (如 IDEA)已经支持一键使用 try-with-resource 包裹了。例如下图:

image

2、自行创建类实现 AutoClosable 接口

其实,我们使用 try-with-resource 语法糖的场景不仅限于各类资源的关闭。我们也可以自己定义一个类,只要他实现了 AutoClosable 接口,也能用 try-with-resource 语法去包裹,并让 JVM 为你填上 finally 代码块。例如如下代码:

代码语言:javascript
复制
public class CustomAutoCloseImpl implements AutoCloseable{
    public void run() {
        // todo biz code
    }

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

public class AutoCloseTest {

    public static void main(String[] args) {

        try (CustomAutoCloseImpl customAutoClose = new CustomAutoCloseImpl()) {
            customAutoClose.run();
        } catch (Exception e) {
            System.out.println(e.getMessage());
        }
    }
}

这个 close 方法一定会给你执行掉。例如有一个场景,这个人访问了网站,无论业务操作是否成功都要给接口调用次数加 1。这个加 1 的操作,就可以放在 close 中,然后调用的时候使用 try-with-resource 语法糖,避免冗余代码判断。当然这里举的例子可能不太恰当,只当是给大家提供一个别样的使用 try-with-resource 的思路(我知道这里肯定有人说这样使用会违背这个语法糖的设计初衷,但我想说的是,思想是活的。没必要把自己定死)

3、try-with-resource 一定要处理异常吗?

这一点也是我的突发奇想,大家可以看一下下面的代码猜一下运行结果

代码语言:javascript
复制
public class CustomAutoCloseImpl implements AutoCloseable{
    public void run() {
        throw new RuntimeException("I suppose to throw an exception!!");
    }

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

public class AutoCloseTest {

    public static void main(String[] args) {

        try (CustomAutoCloseImpl customAutoClose = new CustomAutoCloseImpl()) {
            customAutoClose.run();
        } catch (Exception e) {
            System.out.println(e.getMessage());
        }

        System.out.println("----------------------");

        try (CustomAutoCloseImpl customAutoClose = new CustomAutoCloseImpl()) {
            customAutoClose.run();
        }
    }
}

大家在心里猜一下执行结果吧(其实看懂上面反编译代码的这里一下就能猜对的~)。

image

看着执行结果有几个有意思的点。大家发现了吗,是先执行的 close 方法再去执行的 catch 内部方法块。至于为什么,去看看上面反编译的结果大家就懂啦。还有个有意思的地方,try-with-resource 是可以不用 catch 的哦~ 当然,虽然 close 方法也会给你执行,但是异常该抛还是会给你抛出来的。以后使用的时候,这里也是一个可以利用的地方哦,比如方法内部抛一个自定义异常,外面不捕捉扔给全局异常处理器去处理之类的~

4、try-with-resource 可以包裹多个资源哦

直接上代码吧

代码语言:javascript
复制
    public static void main(String[] args) {

        try (CustomAutoCloseImpl customAutoClose = new CustomAutoCloseImpl();
             FileInputStream fileInputStream = new FileInputStream(new File(""));
             FileOutputStream fileOutputStream = new FileOutputStream(new File(""));
        ) {
            // todo biz code
            customAutoClose.run();
            fileInputStream.read();
            fileOutputStream.flush();
        } catch (Exception e) {
            System.out.println(e.getMessage());
        }

    }

定义多资源的反编译 class 文件很有意思哦,大家可以自己去编译了看看~

总结

任何东西都有它的利害两面,最后总结说一下我认为的优缺点吧:

优点

  • 最大的好处是不用写 finally 了这不用说了吧
  • 提高了程序员针对业务的关注性,不用写着业务代码还要去考虑关闭没关闭的问题
  • 减少了 漏写、忘写 等错误产生的各类内存泄漏问题,毕竟虚拟机大佬还是靠谱的
  • 增加了代码的可玩儿性

缺点

  • 如果想在 close 方法抛出异常时做点事情,怕是做不到。
  • 隐藏起来的东西很可能会引出怎么查也查不到的问题(当然这种情况的出现应该是极极极极少的)
  • 让程序员更懒了,嗯这是个很大的缺点!

因为身边的有些大佬竟然还不知道这个语法糖,所以突发奇想写出来。

一个人不熟悉一个语法糖不能说他不是大佬,但是多熟悉一个语法糖,写的代码就会更舒服一点~ 希望能帮到大家。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 应用场景
  • 代码改造
  • 实现原理
  • 题外话
  • 1、IDEA 自动生成
  • 2、自行创建类实现 AutoClosable 接口
  • 3、try-with-resource 一定要处理异常吗?
  • 4、try-with-resource 可以包裹多个资源哦
  • 总结
  • 优点
  • 缺点
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档