
与面试官聊try-catch-finally关闭资源,你的答案还是10年前的?
前言
有编程经验的朋友都知道,在程序运行中如果打开了一些资源,那么当发生异常或程序结束时都需要进行资源的关闭,不然会造成内存溢出的问题。
曾经,关于try-catch-finally的使用也是面试题中的一个热点问题。随着JDK7的发布,情况好像有些变化了,处理资源关闭的方式更加方便了。但如果你的使用方式依旧停留在十年前,那这篇文章中讲到的知识点值得你一读。最重要的是底层原理分析部分。
try-catch-finally传统处理模式
在JDK7之前,我们对异常和资源关闭的处理,通常是通过下面的形式来实现的:
首先,通过try-catch来捕获异常,并在catch代码块中对异常进行处理(比如打印日志等);
其次,在finally代码块中对打开的资源进行关闭。因为无论程序是否发生异常,finally代码块是必然会被执行的,这也就保证了资源的关闭。
当你写了多年的代码,上面的写法也已经牢记于心,但如果用JDK7及以上版本,且IDE中安装了一些代码规范的插件,在try上面会有如下提示:
提示告诉你,try中的代码可以使用自动资源管理了。那我们就来看看它是如何实现自动管理的呢。
JDK7的资源关闭方式
JDK7中引入了一个新特性:“try-with-resource”。先将上面的代码改造成新的实现方式:
在try后面添加一个小括号,在小括号内声明初始化操作的资源。此时,我们再也不用写finally代码块进行资源的关闭了,JVM会替我们进行资源管理,自动关闭资源。
如果需要声明多个资源,则可以通过分号进行分割:
那么是不是,所有的资源都可以被JVM自动关闭呢?还真不是的,对应的资源类要实现java.io.Closeable接口才行。比如上面的Scanner便是实现了此接口:
自定义关闭实现
既然实现java.io.Closeable接口的类可以享受自动关闭资源的好处,那我们自定义类是否同样享受这个福利呢?
先定义一个MyResource类,实现java.io.Closeable接口:
在自定义类中要实现close()方法。然后看一下使用时是否会被自动关闭:
执行单元测试,输入结果:
可以看到在调用hello方法之后,JVM自动调用了close方法,完美的关闭了资源。
底层实现
了解我写文章风格的读者都会知道,在写一个知识点时我们不只会停留在表面,还要看一下它的底层实现。这里我们先将测试代码简化:
然后对其class文件进行反编译,可以看到Java编译器对这一些写法的真正实现:
会发现虽然我们没写finally代码块进行资源的关闭,但Java编译器已经帮我们做了处理。看到这里,你可能已经意识到了,try-catch-resource这种写法只是一个语法糖。
但好像不仅仅如此,finally代码中还包含了一个addSuppressed方法的调用,这又是怎么回事呢?下面来分析一下。
避免异常覆盖
在上面的示例中,我们将MyResource的两个方法进行改造:
在两个方法中都抛出异常,此时,我们再来执行一下传统写法的单元测试代码:
打印结果如下:
你发现什么了?本来是hello方法先抛出了异常,然后执行close方法又抛出了异常,但后面的异常信息将前面真正的异常信息给“隐藏”了。此时你去排查bug,是不是很困惑?最关键的异常信息被覆盖了。
那么,我们再来执行一下try-catch-resource写法的代码:
执行结果如下:
此时hello方法中的异常信息和close方法中的异常信息全被打印出来了。而异常信息中多出的Suppressed提示便是通过Java编译器自动添加的addSuppressed方法的调用来实现的。此时,再通过异常日志排查bug是不是简单多了,编译器是真为程序员着想啊。
小结
本文通过对try-catch-finally和try-with-resource两种写法的对比,得知try-with-resource是JDK7为我们提供的一个语法糖,可以让我们的代码更加简洁,本质上与try-catch-finally的效果一样。同时,try-with-resource写法通过addSuppressed方法对异常覆盖问题进行了处理,更便于程序员排查bug。
文章转载自公众号: 程序新视界
