探究 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语法糖是这样工作的,豁然开朗。

