深入理解 Java 中的 try with resources

一、摘要

try-with-resources是 JDK 7 中引入的一个新的异常处理机制,它能让开发人员不用显式的释放try-catch语句块中使用的资源

比如,我们以文件资源拷贝为示例,大家所熟悉的try-catch-finally写法如下:

public class ResourceTest1 {public static void main(String[] args) {BufferedInputStream bin = null;BufferedOutputStream bout = null;try {bin = new BufferedInputStream(new FileInputStream(new File( "test.txt")));bout = new BufferedOutputStream(new FileOutputStream(new File( "out.txt")));int b;while ((b = bin.read()) != -1) {bout.write(b);}} catch (IOException e) {e.printStackTrace();} finally {//关闭文件流if (bin != null) {try {bin.close();} catch (IOException e) {e.printStackTrace();}}if (bout != null) {try {bout.close();} catch (IOException e) {e.printStackTrace();}}}}
}

我们现在将其改成使用try-with-resources编程方式,你会惊奇的发现只需要简单的几行代码就可以搞定,不用显式关闭资源,方式如下:

public class ResourceTest2 {public static void main(String[] args) {try (BufferedInputStream bin = new BufferedInputStream(new FileInputStream(new File("test.txt")));BufferedOutputStream bout = new BufferedOutputStream(new FileOutputStream(new File("out.txt")))) {int b;while ((b = bin.read()) != -1) {bout.write(b);}}catch (IOException e) {e.printStackTrace();}}
}

在 JDK7 之前,在处理必须关闭的资源时,开发人员必须要牢记在try-catch语句中使用finally执行关闭资源的方法,否则随着程序不断运行,资源泄露将会累计成重大的生产事故,如果你的程序中同时打开了多个资源,你会惊奇的发,关闭资源的代码竟然比业务代码还要多,使得代码更加难以清晰的阅读和管理。

因此在这样的背景下,try-with-resources由此诞生,它的设计初衷就是旨在减轻开发人员释放try块中使用的资源负担。

习惯了try-catch-finally写法的同学,可能会发出疑问,是不是所有涉及到资源的操作都可以用try-with-resources编程?使用这种编程方式有没有坑?如果有坑,使用的时候哪些地方应该需要注意呢?…

好吧,废话也不多说了,今天我们就一起来看看try-with-resources编程原理。

二、实践解说

try-with-resources语句能确保每个资源在语句结束时被关闭,但是有一个前提条件,那就是这个资源必须实现了java.lang.AutoCloseable接口,才可以被执行关闭

try-with-resources编程模式中,无需开发人员显式关闭资源的前提是,这个资源必须实现java.lang.AutoCloseable接口,并且重写close方法,否则无法在try-with-resources中进行声明变量。

下面我们可以关闭单个资源为例,代码如下:

public class TryResourceDemo implements AutoCloseable {public void doSomething(){System.out.println("do something");}@Overridepublic void close() throws Exception {System.out.println("resource is closed");}
}
public class TryResourceTest {public static void main(String[] args) {try(TryResourceDemo res = new TryResourceDemo()) {res.doSomething();} catch(Exception ex) {ex.printStackTrace();}}
}

运行结果如下:

do something
resource is closed

可以很清晰的看到,close方法被调用了!

下面我们再打开反编译后的TryResourceTest.class文件代码,你会惊奇发现,编译器自动给代码加上了finally方法,并且会调用close方法,将资源关闭!

public class TryResourceTest {public static void main(String[] args) {try {TryResourceDemo res = new TryResourceDemo();Throwable var2 = null;try {res.doSomething();} catch (Throwable var12) {var2 = var12;throw var12;} finally {if (res != null) {if (var2 != null) {try {res.close();} catch (Throwable var11) {var2.addSuppressed(var11);}} else {res.close();}}}} catch (Exception var14) {var14.printStackTrace();}}
}

也就是说,使用try-with-resources编程,其实是编译器显式的给代码了添加finally方法,省去开发人员手动关闭资源的操作!

三、资源关闭顺序

上面我们只介绍了关闭单个资源的场景,假如有多个资源时,try-with-resources是如何关闭的呢?

下面还是举例看结果。

public class TryResourceDemo1 implements AutoCloseable {public void doSomething(){System.out.println("do something 1");}@Overridepublic void close() throws Exception {System.out.println("resource 1 is closed");}
}
public class TryResourceDemo2 implements AutoCloseable {public void doSomething(){System.out.println("do something 2");}@Overridepublic void close() throws Exception {System.out.println("resource 2 is closed");}
}
public class TryResourceDemoTest {public static void main(String[] args) {try(TryResourceDemo1 demo1 = new TryResourceDemo1();TryResourceDemo2 demo2 = new TryResourceDemo2()) {System.out.println("do...");demo1.doSomething();demo2.doSomething();} catch(Exception ex) {ex.printStackTrace();}}
}

运行结果如下:

do...
do something 1
do something 2
resource 2 is closed
resource 1 is closed

从结果上可以看出,try语句中越是最后使用的资源,越是最早被关闭。

关于这一点,大家可以从反编译的代码中找到原理!

四、异常处理机制

正常的情况下,try语句结束时会关闭相关的资源,假如语句内部执行时发生异常,同时我们又显式的调用了finally方法,执行的顺序又是怎样的呢?

下面继续举例看结果。

public class TryThrowResourceDemoTest {public static void main(String[] args) {AutoCloseable obj1 = null;AutoCloseable obj2 = null;try (TryResourceDemo1 demo1 = new TryResourceDemo1();TryResourceDemo2 demo2 = new TryResourceDemo2();) {System.out.println("do...");obj1 = demo1;System.out.println(1 / 0);obj2 = demo2;System.out.println("over...");} catch (Exception e) {e.printStackTrace();} finally {try {System.out.println("before finally close");if (obj1 != null) {obj1.close();}if (obj2 != null) {obj2.close();}System.out.println("after finally close");} catch (Exception e) {e.printStackTrace();}}}
}

运行结果如下:

do...
resource 2 is closed
resource 1 is closed
before finally close
resource 1 is closed
after finally close
java.lang.ArithmeticException: / by zeroat com.example.java.trywithresources.a.TryThrowResourceDemoTest.main(TryThrowResourceDemoTest.java:18)

可以很清晰的看到,可以得出如下结论:

  • 1.只要实现了AutoCloseable接口的类,并且在try里声明了对象变量,在try结束后,不管是否发生异常,close方法都会被调用
  • 2.其次,在try里越晚声明的对象,会越早被close
  • 3.try结束后自动调用的close方法,这个动作会早于finally里调用的方法

五、压制异常处理

大部分情况,我们通常不会担心资源的close会发生异常,现在假设如果try里声明的资源对象,当执行close方法抛异常时,他们的执行顺序又是怎样的呢?我们又如何获取这种异常呢?

还是眼见为实,下面以举例看结果。

public class TryThrowableResourceDemo1 implements AutoCloseable {public void doSomething(){System.out.println("do something 1");throw new NullPointerException("TryThrowableResourceDemo1: doSomething() NullPointerException");}@Overridepublic void close() throws Exception {System.out.println("TryThrowableResourceDemo1 is closed");throw new NullPointerException("TryThrowableResourceDemo1: close() NullPointerException");}
}
public class TryThrowableResourceDemo2 implements AutoCloseable {public void doSomething(){System.out.println("do something 2");throw new NullPointerException("TryThrowableResourceDemo2: doSomething() NullPointerException");}@Overridepublic void close() throws Exception {System.out.println("TryThrowableResourceDemo2 is closed");throw new NullPointerException("TryThrowableResourceDemo2: close() NullPointerException");}
}
public class TryThrowableResourceDemoTest {public static void main(String[] args) {try (TryThrowableResourceDemo1 demo1 = new TryThrowableResourceDemo1();TryThrowableResourceDemo2 demo2 = new TryThrowableResourceDemo2()) {System.out.println("do...");demo1.doSomething();demo2.doSomething();} catch (Exception e) {System.out.println("gobal: exception");System.out.println(e.getMessage());Throwable[] suppressed = e.getSuppressed();for (int i = 0; i < suppressed.length; i++){System.out.println(suppressed[i].getMessage());}}}
}

运行结果如下:

do...
do something 1
TryThrowableResourceDemo2 is closed
TryThrowableResourceDemo1 is closed
gobal: exception
TryThrowableResourceDemo1: doSomething() NullPointerException
TryThrowableResourceDemo2: close() NullPointerException
TryThrowableResourceDemo1: close() NullPointerException

从运行结果我们可以很清晰的看到,对于try语句块内的异常,我们可以通过e.getMessage()获取,对于close()方法抛出的异常,其实编译器对这部分的异常进行特殊处理,将其放入到集合数组中了,因此我们需要通过e.getSuppressed()方法来获取。

具体反编译后的代码如下:

public class TryThrowableResourceDemoTest {public static void main(String[] args) {try {TryThrowableResourceDemo1 demo1 = new TryThrowableResourceDemo1();Throwable var34 = null;try {TryThrowableResourceDemo2 demo2 = new TryThrowableResourceDemo2();Throwable var4 = null;try {System.out.println("do...");demo1.doSomething();demo2.doSomething();} catch (Throwable var29) {var4 = var29;throw var29;} finally {if (demo2 != null) {if (var4 != null) {try {demo2.close();} catch (Throwable var28) {var4.addSuppressed(var28);}} else {demo2.close();}}}} catch (Throwable var31) {var34 = var31;throw var31;} finally {if (demo1 != null) {if (var34 != null) {try {demo1.close();} catch (Throwable var27) {var34.addSuppressed(var27);}} else {demo1.close();}}}} catch (Exception var33) {System.out.println("gobal: exception");System.out.println(var33.getMessage());Throwable[] suppressed = var33.getSuppressed();for(int i = 0; i < suppressed.length; ++i) {System.out.println(suppressed[i].getMessage());}}}
}

六、关闭资源的坑

在实际的使用中,不管是使用try-with-resource编程还是使用try-catch-finally编程**,一定需要了解资源的close方法内部的实现逻辑,否则还是可能会导致资源泄露**。

举个例子,在 Java BIO 中采用了大量的装饰器模式。当调用装饰器的 close 方法时,本质上是调用了装饰器包装的流对象的 close 方法。比如:

public class TryWithResource {public static void main(String[] args) {try (FileInputStream fin = new FileInputStream(new File("input.txt"));GZIPOutputStream out = new GZIPOutputStream(new FileOutputStream(new File("out.txt")))) {byte[] buffer = new byte[4096];int read;while ((read = fin.read(buffer)) != -1) {out.write(buffer, 0, read);}}catch (IOException e) {e.printStackTrace();}}
}

在上述代码中,我们从FileInputStream中读取字节,并且写入到GZIPOutputStream中。GZIPOutputStream实际上是FileOutputStream的装饰器。

由于try-with-resource的特性,实际编译之后的代码会在后面带上finally代码块,并且在里面调用fin.close()方法和out.close()方法。

我们再来看GZIPOutputStream类的close方法。

public void close() throws IOException {if (!closed) {finish();if (usesDefaultDeflater)def.end();out.close();closed = true;}
}

在调用out变量的close方法之前,GZIPOutputStream还做了finish操作,该操作还会继续往FileOutputStream中写压缩信息,此时如果出现异常,则out.close()方法会被略过,out变量实际上代表的是被装饰的FileOutputStream类,这个才是最底层的资源关闭方法

正确的做法应该是在try-with-resource中单独声明最底层的资源,保证对应的close方法一定能够被调用。在刚才的例子中,我们需要单独声明每个FileInputStream以及FileOutputStream,改成如下方式:

public class TryWithResource {public static void main(String[] args) {try (FileInputStream fin = new FileInputStream(new File("input.txt"));FileOutputStream fout = new FileOutputStream(new File("out.txt"));GZIPOutputStream out = new GZIPOutputStream(fout)) {byte[] buffer = new byte[4096];int read;while ((read = fin.read(buffer)) != -1) {out.write(buffer, 0, read);}}catch (IOException e) {e.printStackTrace();}}
}

编译器会自动生成fout.close()的代码,这样肯定能够保证真正的流被关闭。

七、小结

在处理必须关闭的资源时,使用try-with-resources语句替代try-catch-finally语句,你会惊奇的发现,编写的代码更简洁,更清晰,同时也省去了手动显式释放资源的烦恼。

因此在实际编程过程中,推荐大家采用这种方式编写,同时要关注close方法内部的实现逻辑,避免资源泄露,服务宕机!

八、参考

1、知乎 - 深入理解Java try-with-resource

2、csdn - try - with - resources详解

写到最后

不会有人刷到这里还想白嫖吧?点赞对我真的非常重要!在线求赞。加个关注我会非常感激!

本文已整理到技术笔记中,此外,笔记内容还涵盖 Spring、Spring Boot/Cloud、Dubbo、JVM、集合、多线程、JPA、MyBatis、MySQL、微服务等技术栈。

需要的小伙伴可以点击 技术笔记 获取!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/373493.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

看到指针就头疼?这篇文章让你对指针有更全面的了解!

文章目录 1.什么是指针2.指针和指针类型2.1 指针-整数2.2 指针的解引用 3.野指针3.1为什么会有野指针3.2 如何规避野指针 4.指针运算4.1 指针-整数4.2 指针减指针4.3 指针的关系运算 5.指针与数组6.二级指针7.指针数组 1.什么是指针 指针的两个要点 1.指针是内存中的一个最小单…

(HAL)stm32f407+freertos通过usb驱动移远4G模块-EC600U

概述 本篇文章主要介绍: 如何使用STM32CubeMX创建stm32F407+freertos+usb host的基础工程。USB-HOST-CDC驱动运行过程。如何根据4G模块的具体信息修改usb相关代码。MCU如何通过usb与4G模块通信,收发数据。调试过程中遇到的问题以及解决办法。 整个过程中在网上搜罗了很多参考…

suricata7 rule加载(三)加载options

suricata7.0.5 加载options (msg:“HTTP Request Example”; flow:established,to_server; http.method; content:“POST”; http.uri; content:“query.php”; bsize:>9; http.protocol; content:“HTTP/1.1”; bsize:8; http.host; content:“360”; bsize:>3; class…

LabVIEW自动测控与故障识别系统

使用LabVIEW 2019在Win10 64位系统上开发自动测控软件&#xff0c;通过与基恩士NR-X100数据采集仪通讯&#xff0c;实时采集和分析数据&#xff0c;自动识别判断产品是否合格&#xff0c;并增加数据记录和仿真功能。 具体解决方案&#xff1a; 1. 系统架构设计 硬件接口&#…

IP 地址:优化网络游戏

IP地址和网络游戏 在现代网络游戏中&#xff0c;IP地址不仅用于服务器分配&#xff0c;还能针对性进行玩家匹配与优化网络延迟。本文将探讨IP地址在网络游戏中的具体应用。 *服务器分配* 全球服务器分布&#xff1a; 网络游戏需要在全球范围内提供快速、稳定的连接&#xff…

网络安全合规建设

网络安全合规建设 一、法律安全需求基本合规&#xff08;1&#xff09;《网络安全法》重要节点等级保护政策核心变化 二、安全需求 业务刚需&#xff08;1&#xff09;内忧&#xff08;2&#xff09;外患 三、解决方法&#xff08;1&#xff09;总安全战略目标图&#xff08;2&…

springboot文献检索系统-计算机毕业设计源码48521

摘要 文献检索系统主要功能模块包括用户管理、公告信息、新闻资讯、文献信息等&#xff0c;采取面对对象的开发模式进行软件的开发和硬体的架设&#xff0c;能很好的满足实际使用的需求&#xff0c;完善了对应的软体架设以及程序编码的工作&#xff0c;采取MySQL作为后台数据的…

!vue3中defineEmits接收父组件向子组件传递方法,以及方法所需传的参数及类型定义,避免踩坑!

使用说明 1、在子组件中调用defineEmits并定义要发射给父组件的方法 const emits defineEmits([‘foldchange’]) 2、使用defineEmits会返回一个方法&#xff0c;使用一个变量emits(变量名随意)去接收 3、在子组件要触发的方法中&#xff0c;调用emits并传入发射给父组件的方法…

封装了一个仿照抖音效果的iOS评论弹窗

需求背景 开发一个类似抖音评论弹窗交互效果的弹窗&#xff0c;支持滑动消失&#xff0c; 滑动查看评论 效果如下图 思路 创建一个视图&#xff0c;该视图上面放置一个tableView, 该视图上添加一个滑动手势&#xff0c;同时设置代理&#xff0c;实现代理方法 (BOOL)gestur…

挑战杯 opencv 图像识别 指纹识别 - python

0 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 &#x1f6a9; 基于机器视觉的指纹识别系统 &#x1f947;学长这里给一个题目综合评分(每项满分5分) 难度系数&#xff1a;3分工作量&#xff1a;3分创新点&#xff1a;4分 该项目较为新颖&#xff0c;适…

基于swagger插件的方式推送接口文档至torna

目录 一、前言二、登录torna三、创建/选择空间四、创建/选择项目五、创建/选择应用六、获取应用的token七、服务推送7.1 引入maven依赖7.2 test下面按照如下方式新建文件 一、前言 Torna作为一款企业级文档管理系统&#xff0c;支持了很多种接口文档的推送方式。官方比较推荐的…

蓝牙人员定位精准吗?是否会对人体有伤害?

不知道大家现在使用的蓝牙人员定位系统都是什么样的呢&#xff1f;其实就出行而言&#xff0c;使用GPS定位也就是足够了的&#xff0c;而且目前的定位相对也比较精准了&#xff0c;效果还是很不错的。但是如果说是室内定位&#xff0c;很显然常规的定位系统是无法满足使用需求的…

[数据结构] 基于插入的排序 插入排序希尔排序

标题&#xff1a;[数据结构] 排序#插入排序&希尔排序 水墨不写bug 目录 &#xff08;一&#xff09;插入排序 实现思路&#xff1a; 插入排序实现&#xff1a; &#xff08;二&#xff09;希尔排序 希尔排序的基本思想&#xff1a; 希尔排序的实现&#xff1a; 正…

PHP同城多商户多行业系统小程序源码

同城新生态&#xff0c;解锁多商户多行业系统的无限魅力&#x1f306;&#x1f680; &#x1f308; 开篇&#xff1a;同城新纪元&#xff0c;多商户多行业系统引领潮流&#xff01; 想象一下&#xff0c;在同一个城市内&#xff0c;无论是美食探索、购物狂欢&#xff0c;还是…

Python在量化交易中的应用

量化交易近年来越来越受到投资者的青睐。Python因其简洁的语法和丰富的库&#xff0c;成为量化交易的首选编程语言。本文将从Python量化交易的基础知识、主要技术及其在实际交易中的应用三个方面进行介绍。 一、Python量化交易的基础知识 1. 量化交易的概念 量化交易是指利用…

东方通Tongweb发布vue前端

一、前端包中添加文件 1、解压vue打包文件 以dist.zip为例&#xff0c;解压之后得到dist文件夹&#xff0c;进入dist文件夹&#xff0c;新建WEB-INF文件夹&#xff0c;进入WEB-INF文件夹&#xff0c;新建web.xml文件&#xff0c; 打开web.xml文件&#xff0c;输入以下内容 …

sdwan是硬件还是网络协议?

SD-WAN&#xff08;Software-Defined Wide Area Network&#xff0c;软件定义广域网&#xff09;并不是一个硬件产品或单一的网络协议&#xff0c;而是结合了软件、硬件和网络技术的一种解决方案。SD-WAN的核心在于其软件定义的特性&#xff0c;它通过软件来控制和管理广域网的…

【BUG】RestTemplate发送Post请求后,响应中编码为gzip而导致的报错

BUG描述 20240613-09:59:59.062|INFO|null|810184|xxx|xxx||8|http-nio-xxx-exec-1|com.xxx.jim.xxx.XXXController.?.?|MSG接收到来自xxx的文件请求 headers:[host:"xxx", accept:"text/html,application/json,application/xhtmlxml,application/xml;q0.9,*…

Apache Seata分布式事务原理解析探秘

本文来自 Apache Seata官方文档&#xff0c;欢迎访问官网&#xff0c;查看更多深度文章。 本文来自 Apache Seata官方文档&#xff0c;欢迎访问官网&#xff0c;查看更多深度文章。 前言 fescar发布已有时日&#xff0c;分布式事务一直是业界备受关注的领域&#xff0c;fesca…

如何探索高效知识管理:FlowUs知识库体验很好

在当今信息爆炸的时代&#xff0c;有效的知识管理对于个人和团队的发展至关重要。FlowUs 知识库作为一款创新的知识管理工具&#xff0c;正逐渐成为众多用户的首选&#xff0c;为他们带来了高效、便捷和有条理的知识管理体验。 FlowUs 知识库的一大特色在于其简洁直观的界面设计…