注:本文出自博主 Chloneda个人博客 | 博客园 | Github | Gitee | 知乎

本文源链接https://www.cnblogs.com/chloneda/p/java-close.html

Java基础系列,尽量采用通俗易懂、循序渐进的方式,让大家真正优雅地使用close()方法!

问题场景

平时我们使用资源后一般都会关闭资源,即close()方法,但这个步骤重复性很高,还面临上述执行顺序不明的风险,而且很多人还是不能正确合理地关闭资源。

我们来看看close()是怎么错误地关闭资源的?

错误的close()

先来看看如下的错误关闭资源方式:

package com.chloneda.jutils.test;

import java.sql.*;

/**
* @author chloneda
* @description: close()方法测试
* 错误的close()
*/
public class CloseTest {
public static void main(String[] args) {
Connection conn = null;
Statement st = null;
ResultSet rs = null;
try {
//1.加载驱动程序
Class.forName("com.mysql.jdbc.Driver");
//2.获得数据库链接
conn = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/common", "root", "123456");
//3.通过数据库的连接操作数据库,实现增删改查
st = conn.createStatement();
rs = st.executeQuery("select * from new_table");
//4.处理数据库的返回结果
while (rs.next()) {
System.out.println(rs.getString("id") + " " + rs.getString("name"));
}
//5.关闭资源
rs.close();
st.close();
conn.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}

上面代码的资源关闭写在了try代码块中,一旦close方法调用之前(比如3步骤)就抛出异常,那么关闭资源的代码就永远不会得到执行。

如果我们把关闭资源的代码放在finally中行不行呢?

    try {
//1.加载驱动程序
Class.forName("com.mysql.jdbc.Driver");
//2.获得数据库链接
conn = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/common", "root", "123456");
//3.通过数据库的连接操作数据库,实现增删改查
st = conn.createStatement();
rs = st.executeQuery("select * from new_table");
//4.处理数据库的返回结果
while (rs.next()) {
System.out.println(rs.getString("id") + " " + rs.getString("name"));
}
} catch (Exception e) {
e.printStackTrace();
} finally {
//5.关闭资源
try {
rs.close();
st.close();
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}

答案是不行!如果在 2步骤 的try中conn获得数据库链接抛出异常,那么conn仍然为null,此时进入finally代码块中,执行close()就报空指针异常了,关闭资源没有意义!因此,我们需要在close()之前判断一下conn等是否为空,只有不为空的时候才需要close。

常见的close()

针对上述场景,得到常见的使用close()方式如下:

/**
* @author chloneda
* @description: close()方法测试
* 常见的close()
*/
public class CloseTest {
public static void main(String[] args) {
Connection conn = null;
Statement st = null;
ResultSet rs = null;
try {
//1.加载驱动程序
Class.forName("com.mysql.jdbc.Driver");
//2.获得数据库链接
conn = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/common", "root", "123456");
//3.通过数据库的连接操作数据库,实现增删改查
st = conn.createStatement();
rs = st.executeQuery("select * from new_table");
//4.处理数据库的返回结果
while (rs.next()) {
System.out.println(rs.getString("id") + " " + rs.getString("name"));
}
} catch (Exception e) {
e.printStackTrace();
} finally {
//5.关闭资源
if (null != rs) {
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (null != st) {
try {
st.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (null != conn) {
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
} }
}
}

这是常见的close()!但是finally代码块的代码重复性太高了,这还只是三个资源的关闭,如果有很多个资源需要在finally中关闭,那不是需要编写很多不优雅的代码?其实,关闭资源是没啥逻辑的代码,我们需要精简代码,减少代码重复性,优雅地编程!

使用AutoCloseable接口

自从Java7以后,我们可以使用 AutoCloseable接口 (Closeable接口也可以)来优雅的关闭资源了 看看修改例子:

/**
* @author chloneda
* @description: close()方法测试
* 使用AutoCloseable接口
*/
public class CloseTest {
public static void main(String[] args) {
Connection conn = null;
Statement st = null;
ResultSet rs = null;
try {
//1.加载驱动程序
Class.forName("com.mysql.jdbc.Driver");
//2.获得数据库链接
conn = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/common", "root", "123456");
//3.通过数据库的连接操作数据库,实现增删改查
st = conn.createStatement();
rs = st.executeQuery("select * from new_table");
//4.处理数据库的返回结果
while (rs.next()) {
System.out.println(rs.getString("id") + " " + rs.getString("name"));
}
} catch (Exception e) {
e.printStackTrace();
} finally {
//5.关闭资源,调用自定义的close()方法
close(rs);
close(st);
close(conn);
}
} public static void close(AutoCloseable closeable) {
if (closeable != null) {
try {
closeable.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}

上面的finally代码块的代码量是不是减少了许多,就单纯地调用的静态的 close(AutoCloseable closeable) 方法。为什么可以这样呢?

其实Connection、Statement、ResultSet三个接口都继承了AutoCloseable接口。所以只要涉及到资源的关闭,继承了AutoCloseable接口,实现了close()方法,我们都可以调用 close(AutoCloseable closeable) 方法进行资源关闭。

此外,java IO流的很多类都实现了 Closeable接口,而Closeable接口又继承自 AutoCloseable接口,也可以调用上面的 close(AutoCloseable closeable) 方法进行资源关闭。是不是一语惊醒梦中人啊?

使用try-with-resources

其实Java7以后,还有一种关闭资源的方式,也就是 try-with-resources,这种方式也是我们推荐的!很优雅!

我们来看看它是怎么优雅地关闭资源的!

/**
* @author chloneda
* @description: close()方法测试
* 使用try-with-resources
*/
public class CloseTest {
public static void main(String[] args) throws ClassNotFoundException {
//1.加载驱动程序
Class.forName("com.mysql.jdbc.Driver");
try (//2.获得数据库链接
Connection conn = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/common", "root", "123456");
//3.通过数据库的连接操作数据库,实现增删改查
Statement st = conn.createStatement();
ResultSet rs = st.executeQuery("select * from new_table")
) {
//4.处理数据库的返回结果
while (rs.next()) {
System.out.println(rs.getString("id") + " " + rs.getString("name"));
}
} catch (Exception e) {
e.printStackTrace();
}
}
}

这种方式就省略了finally,不必重复编写关闭资源的代码了!而且资源也得到了关闭!怎么验证这个问题?可以查看底下的 实际应用 章节!

其实try-with-resources关闭资源的操作,本质上是继承了java.lang.AutoCloseable接口,实现了close方法,所以使用try-with-resources能关闭资源。很神奇吧!

实际应用

这个章节就是验证使用try-with-resources可以关闭资源的问题的!

上面我们说了使用try-with-resources关闭资源,只要是继承了java.lang.AutoCloseable接口 实现close()方法就可以使用!

我们自定义一个资源类,来看看实际应用吧!

package com.chloneda.jutils.test;

/**
* @author chloneda
* @description: 资源类, 实现AutoCloseable接口.
*/
public class Resources implements AutoCloseable {
public void useResource() {
System.out.println("useResource:{} 正在使用资源!");
} @Override
public void close() {
System.out.println("close:{} 自动关闭资源!");
}
} /**
* @description: 使用try-with-resources自动关闭资源测试.
*/
class AutoClosableTest {
public static void main(String[] args) {
/** 使用try-with-resource,自动关闭资源 */
try (
Resources resource = new Resources()
) {
resource.useResource();
} catch (Exception e) {
e.getMessage();
} finally {
System.out.println("Finally!");
}
}
}

结果输出。

useResource:{} 正在使用资源!
close:{} 自动关闭资源!
Finally!

看到运行结果了吗?Resources类实现AutoCloseable接口,实现了close()方法,try-with-resources 就会自动关闭资源!

一旦Resources类没有继承java.lang.AutoCloseable接口,没有实现close()方法,AutoClosableTest类的try模块就在编译期报错,提示信息如下。

Incompatible types.
Required: java.lang.AutoCloseable
Found: com.chloneda.jutils.test.Resources

最后,需要说明的是try-with-resources就是一个JVM语法糖!关于JVM语法糖可以查查相关资料,看看Java中有哪些有趣的语法糖!

尾语

《Effective Java》在第三版中也推荐使用try-with-resources语句替代try-finally语句。

所以在处理必须关闭的资源时,使用try-with-resources语句替代try-finally语句。生成的代码更简洁,更清晰,并且生成的异常更有用。 try-with-resources语句在编写必须关闭资源的代码时会更容易,也不会出错,而使用try-finally语句实际上是不可能的。

如此,推荐大家使用try-with-resources优雅地关闭资源!


Java如何优雅地使用close()?的更多相关文章

  1. 我们一起来排序——使用Java语言优雅地实现常用排序算法

    破阵子·春景 燕子来时新社,梨花落后清明. 池上碧苔三四点,叶底黄鹂一两声.日长飞絮轻. 巧笑同桌伙伴,上学径里逢迎. 疑怪昨宵春梦好,元是今朝Offer拿.笑从双脸生. 排序算法--最基础的算法,互 ...

  2. 怎样用Java 8优雅的开发业务

    怎样用Java 8优雅的开发业务 目录 怎样用Java 8优雅的开发业务 函数式编程 流式编程 基本原理 案例 优雅的空处理 新的并发工具类CompletableFuture 单机批处理多线程执行模型 ...

  3. 你的Kubernetes Java应用优雅停机了吗?

    Java 应用优雅停机 我们首先考虑下,一般在什么场景下数据会丢失呢? 升级服务时 pod重启时 服务器断电时 因为服务器断电属于极端情况,我们暂且不考虑.那就只有 Java 退出时我们要保证数据的完 ...

  4. 哦,这就是java的优雅停机?(实现及原理)

    优雅停机? 这个名词我是服的,如果抛开专业不谈,多好的名词啊! 其实优雅停机,就是在要关闭服务之前,不是立马全部关停,而是做好一些善后操作,比如:关闭线程.释放连接资源等. 再比如,就是不会让调用方的 ...

  5. java如何优雅的实现时间控制

    前言:最近小王同学又遇到了一个需求:线上的业务运行了一段时间,后来随着使用人数增多,出现了一个问题是这样的,一个订单会重复创建几次,导致数据库里出现了很多垃圾数据.在测试同学的不断测试下,发现问题出在 ...

  6. 编码规范 | Java函数优雅之道(上)

    导读 随着软件项目代码的日积月累,系统维护成本变得越来越高,是所有软件团队面临的共同问题.持续地优化代码,提高代码的质量,是提升系统生命力的有效手段之一.软件系统思维有句话“Less coding, ...

  7. 编码规范 | Java函数优雅之道(下)

    上文背景 本文总结了一套与Java函数相关的编码规则,旨在给广大Java程序员一些编码建议,有助于大家编写出更优雅.更高质.更高效的代码. 内部函数参数尽量使用基础类型 案例一:内部函数参数尽量使用基 ...

  8. Java函数优雅之道

    https://www.cnblogs.com/amap_tech/p/11320171.html 导读 随着软件项目代码的日积月累,系统维护成本变得越来越高,是所有软件团队面临的共同问题.持续地优化 ...

  9. Java 函数优雅之道

    导读 随着软件项目代码的日积月累,系统维护成本变得越来越高,是所有软件团队面临的共同问题.持续地优化代码,提高代码的质量,是提升系统生命力的有效手段之一.软件系统思维有句话“Less coding, ...

随机推荐

  1. vue 新建脚手架项目npm命令

    使用国外原镜像 npm install -g @vue/cli   //yarn global add @vue/cli            使用淘宝镜像 cnpm install -g @vue/ ...

  2. C#系列之Convert类型转换(三)

    知识点一: 类型如果相兼容的两个变量,可以使用自动类型转化或者强制类型转换,但是,如果两个变量不兼容,比如说String和int或者String和Double类型,这个时候我们就需要一种名叫conve ...

  3. 使用Qt自动注册Lav

    Qt播放视频使用QMediaPlayer要注册Lav解码器,如果手动去注册,每次去使用管理员运行命令或者生成.bat文件都比较麻烦. 解决方法步骤如下: 一:编写注册Lav解码器脚本,并取消控制台的显 ...

  4. python中Threadlocal变量

    在多线程环境下,每个线程都有自己的数据.一个线程使用自己的局部变量比使用全局变量好,因为局部变量只有线程自己能看见,不会影响其他线程,而全局变量的修改必须加锁. 不加锁就会出现变量会被修改的问题,进而 ...

  5. 输入url地址到展示主页的过程

    过程 1.DNS解析 2.TCP连接 3.发送HTTP请求 4.服务器处理请求并返回HTTP报文 5.浏览器解析渲染页面 6.连接结束

  6. 浅谈ActionResult之FileResult

    FileResult是一个基于文件的ActionResult,利用FileResult,我们可以很容易的将某个物理文件的内容响应给客户端,ASP.NET MVC定义了三个具体的FileResult,分 ...

  7. 【OpenGL】GL_DEPTH_TEST深度测试问题

    记录一个深度测试的问题 在实现一个简单的OpenGL程序时,遇到了一个问题,深度测试总是有问题,无法正常显示,如下 正常情况为 通过调试发现屏幕空间中的所有深度值均为1. OpenGL代码如下: vo ...

  8. 提升命令行效率的Bash快捷键

    转自:http://linuxtoy.org/archives/bash-shortcuts.html 生活在 Bash shell 中,熟记以下快捷键,将极大的提高你的命令行操作效率. 大部分对其他 ...

  9. 题解P1104生日

    \(\color{blue}{P1104 生日(题目传送门)}\) 这道题的大意是:给出n个人的出生日期,让我们按年龄(就是先比年份,再比月份和日期,这是常识好吧)大小来排序,如果相同则按输入顺序排序 ...

  10. 12-Tomcat&Servlet:

    今日知识 1. web相关概念回顾 2. web服务器软件:tomcat 3. servletr入门学习 web相关概念回顾 1. 软件架构 1. C/S:客户端/服务器端 2. B/S:浏览器/服务 ...