java中的try-with-resource语法
java的世界千奇百怪。。。当我甩出如下代码段,不知阁下如何应对?
try(A a=new A()){
和a变量无关的业务代码块
}
没错,这就是“臭名昭著”的try-with-resource语法,乍一看让人不知所云,其实它和try-finally的下述代码等价
A a=new A()
try{
//业务代码块
}finally{
a.close();
}
本质上就是当使用 try-with-resources
语句块来处理实现了 AutoCloseable
接口的资源时,Java 编译器会在生成的字节码中进行特殊处理。编译器会自动在 try
块结束时插入 finally
块,并在 finally
块中调用 close()
方法来关闭资源。
try-with-resources语法
try-with-resources
语句是一个 Java 7 引入的语法结构,用于自动关闭实现了 AutoCloseable
或 Closeable
接口的资源。它的语法形式如下:
try (resource_declaration) {
// 使用资源的代码块
} catch (ExceptionType e) {
// 异常处理逻辑
}
在 try-with-resources
语句中,resource_declaration
部分用于声明和初始化一个或多个资源对象。这些资源对象必须实现 AutoCloseable
或 Closeable
接口。
在 try
代码块中,可以使用这些资源对象进行操作。当 try
代码块执行结束时,不论是正常结束还是发生异常,都会自动调用资源对象的 close()
方法来关闭资源。
如果同时声明了多个资源对象,可以使用分号 ;
分隔它们。
那么,它和try-finally语句有什么区别呢?
和try-finally的关系
很明显,try-with-resource语法相对于try-finally语法来说,隐式的调用了资源对象的close方法,语法更简洁,其次,让我们看看如下代码块:
@Test
public void testException(){
try{
throw new CustomException("代码块内的业务异常");
}finally {
throw new CustomException("finally中的资源释放异常");
}
}
运行上面的代码,你将会得到如下异常:
模仿上述代码,看看在try-with-resource语法中又会怎样
先创建一个类MyAutoClose
@Slf4j
public class MyAutoClose implements AutoCloseable {
@Override
public void close() throws Exception {
log.info("自动关闭");
throw new CustomException("close方法异常");
}
}
运行如下单元测试
@Test
public void testAutoClose() {
try (MyAutoClose autoClose = new MyAutoClose()) {
throw new CustomException("业务异常");
} catch (Exception e) {
log.error("", e);
}
}
得到如下异常
综合两种结果来看,可以看到同样的逻辑,业务和finally都是抛出了异常,但是抛出的异常却不一样:在try-finally语句块中,抛出的是finally中的异常,在try-with-resource语句块中,抛出的是业务的异常,而且异常中还携带了close方法关闭时抛出的异常信息。
谁好谁坏,自行体会。
try-with-resources的本质
再进一步细想一下,为啥两种方式逻辑基本一样,但是抛出的异常却不一样?
try-finally代码块很直观,try-with-resource代码块在编译成字节码文件的时候却被编译期“魔改”了,上文提到过
......Java 编译器会在生成的字节码中进行特殊处理。编译器会自动在
try
块结束时插入finally
块,并在finally
块中调用close()
方法来关闭资源。
那只需要通过反编译看下class文件就可以了
再回想一下它原来长什么样子
@Test
public void testAutoClose() {
try (MyAutoClose autoClose = new MyAutoClose()) {
throw new CustomException("业务异常");
} catch (Exception e) {
log.error("", e);
}
}
亲妈都认不出来了。。。
我将反编译后的真正代码分成了四部分,并标记在了图上
- 外层异常try-catch块尝试捕获初始化资源异常
- 里层try-catch块尝试捕获业务执行异常
- 业务try-catch块捕获到异常,直接抛出
- finally块判定是否发生了业务异常:如果发生了业务异常,而且close方法执行也发生了异常,则将close方法发生的异常附加业务异常中;如果未发生业务异常,则直接执行close方法,这时候如果发生了异常,直接抛出的就是finally块中的异常了。
总结下,实际上是如下形式
try{
//初始化资源
try{
//业务代码执行
}catch(Exceptin e){
//捕获的业务异常,抛出业务异常
}finally{
//执行close方法,并判定在不同情况下的异常信息
}
}catch(Exception e){
//捕获的资源初始化异常
}
结论:使用try-with-resource很明显比使用try-finally块要更好一些,它能准确捕获业务异常;但是try-finally块也有不可替代的使用场景,比如资源类未实现AutoCloseable接口的时候
最后,欢迎关注我的博客原文:https://blog.kdyzm.cn/post/179
END.