注:本文出自博主 Chloneda:個(gè)人博客 | 博客園 | Github | Gitee | 知乎
本文源鏈接:https://www.cnblogs.com/chloneda/p/java-close.html
本文盡量采用通俗易懂、循序漸進(jìn)的方式,讓大家真正優(yōu)雅地使用close()方法!
問(wèn)題場(chǎng)景
平時(shí)我們使用資源后一般都會(huì)關(guān)閉資源,即close()方法,但這個(gè)步驟重復(fù)性很高,還面臨上述執(zhí)行順序不明的風(fēng)險(xiǎn),而且很多人還是不能正確合理地關(guān)閉資源。
我們來(lái)看看close()是怎么錯(cuò)誤地關(guān)閉資源的?
錯(cuò)誤的close()
先來(lái)看看如下的錯(cuò)誤關(guān)閉資源方式:
package com.chloneda.jutils.test;
import java.sql.*;
/**
* @author chloneda
* @description: close()方法測(cè)試
* 錯(cuò)誤的close()
*/
public class CloseTest {
public static void main(String[] args) {
Connection conn = null;
Statement st = null;
ResultSet rs = null;
try {
//1.加載驅(qū)動(dòng)程序
Class.forName("com.mysql.jdbc.Driver");
//2.獲得數(shù)據(jù)庫(kù)鏈接
conn = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/common", "root", "123456");
//3.通過(guò)數(shù)據(jù)庫(kù)的連接操作數(shù)據(jù)庫(kù),實(shí)現(xiàn)增刪改查
st = conn.createStatement();
rs = st.executeQuery("select * from new_table");
//4.處理數(shù)據(jù)庫(kù)的返回結(jié)果
while (rs.next()) {
System.out.println(rs.getString("id") + " " + rs.getString("name"));
}
//5.關(guān)閉資源
rs.close();
st.close();
conn.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
上面代碼的資源關(guān)閉寫(xiě)在了try代碼塊中,一旦close方法調(diào)用之前(比如3步驟)就拋出異常,那么關(guān)閉資源的代碼就永遠(yuǎn)不會(huì)得到執(zhí)行。
如果我們把關(guān)閉資源的代碼放在finally中行不行呢?
try {
//1.加載驅(qū)動(dòng)程序
Class.forName("com.mysql.jdbc.Driver");
//2.獲得數(shù)據(jù)庫(kù)鏈接
conn = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/common", "root", "123456");
//3.通過(guò)數(shù)據(jù)庫(kù)的連接操作數(shù)據(jù)庫(kù),實(shí)現(xiàn)增刪改查
st = conn.createStatement();
rs = st.executeQuery("select * from new_table");
//4.處理數(shù)據(jù)庫(kù)的返回結(jié)果
while (rs.next()) {
System.out.println(rs.getString("id") + " " + rs.getString("name"));
}
} catch (Exception e) {
e.printStackTrace();
} finally {
//5.關(guān)閉資源
try {
rs.close();
st.close();
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
答案是不行!如果在 2步驟 的try中conn獲得數(shù)據(jù)庫(kù)鏈接拋出異常,那么conn仍然為null,此時(shí)進(jìn)入finally代碼塊中,執(zhí)行close()就報(bào)空指針異常了,關(guān)閉資源沒(méi)有意義!因此,我們需要在close()之前判斷一下conn等是否為空,只有不為空的時(shí)候才需要close。
常見(jiàn)的close()
針對(duì)上述場(chǎng)景,得到常見(jiàn)的使用close()方式如下:
package com.chloneda.jutils.test;
/**
* @author chloneda
* @description: close()方法測(cè)試
* 常見(jiàn)的close()
*/
public class CloseTest {
public static void main(String[] args) {
Connection conn = null;
Statement st = null;
ResultSet rs = null;
try {
//1.加載驅(qū)動(dòng)程序
Class.forName("com.mysql.jdbc.Driver");
//2.獲得數(shù)據(jù)庫(kù)鏈接
conn = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/common", "root", "123456");
//3.通過(guò)數(shù)據(jù)庫(kù)的連接操作數(shù)據(jù)庫(kù),實(shí)現(xiàn)增刪改查
st = conn.createStatement();
rs = st.executeQuery("select * from new_table");
//4.處理數(shù)據(jù)庫(kù)的返回結(jié)果
while (rs.next()) {
System.out.println(rs.getString("id") + " " + rs.getString("name"));
}
} catch (Exception e) {
e.printStackTrace();
} finally {
//5.關(guān)閉資源
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();
}
}
}
}
}
這是常見(jiàn)的close()!但是finally代碼塊的代碼重復(fù)性太高了,這還只是三個(gè)資源的關(guān)閉,如果有很多個(gè)資源需要在finally中關(guān)閉,那不是需要編寫(xiě)很多不優(yōu)雅的代碼?其實(shí),關(guān)閉資源是沒(méi)啥邏輯的代碼,我們需要精簡(jiǎn)代碼,減少代碼重復(fù)性,優(yōu)雅地編程!
使用AutoCloseable接口
自從Java7以后,我們可以使用 AutoCloseable接口 (Closeable接口也可以)來(lái)優(yōu)雅的關(guān)閉資源了 看看修改例子:
package com.chloneda.jutils.test;
/**
* @author chloneda
* @description: close()方法測(cè)試
* 使用AutoCloseable接口
*/
public class CloseTest {
public static void main(String[] args) {
Connection conn = null;
Statement st = null;
ResultSet rs = null;
try {
//1.加載驅(qū)動(dòng)程序
Class.forName("com.mysql.jdbc.Driver");
//2.獲得數(shù)據(jù)庫(kù)鏈接
conn = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/common", "root", "123456");
//3.通過(guò)數(shù)據(jù)庫(kù)的連接操作數(shù)據(jù)庫(kù),實(shí)現(xiàn)增刪改查
st = conn.createStatement();
rs = st.executeQuery("select * from new_table");
//4.處理數(shù)據(jù)庫(kù)的返回結(jié)果
while (rs.next()) {
System.out.println(rs.getString("id") + " " + rs.getString("name"));
}
} catch (Exception e) {
e.printStackTrace();
} finally {
//5.關(guān)閉資源,調(diào)用自定義的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代碼塊的代碼量是不是減少了許多,就單純地調(diào)用的靜態(tài)的 close(AutoCloseable closeable) 方法。為什么可以這樣呢?
其實(shí)Connection、Statement、ResultSet三個(gè)接口都繼承了AutoCloseable接口。所以只要涉及到資源的關(guān)閉,繼承了AutoCloseable接口,實(shí)現(xiàn)了close()方法,我們都可以調(diào)用 close(AutoCloseable closeable) 方法進(jìn)行資源關(guān)閉。
此外,java IO流的很多類(lèi)都實(shí)現(xiàn)了 Closeable接口,而Closeable接口又繼承自 AutoCloseable接口,也可以調(diào)用上面的 close(AutoCloseable closeable) 方法進(jìn)行資源關(guān)閉。是不是一語(yǔ)驚醒夢(mèng)中人?。?/p>
使用try-with-resources
其實(shí)Java7以后,還有一種關(guān)閉資源的方式,也就是 try-with-resources,這種方式也是我們推薦的!很優(yōu)雅!
我們來(lái)看看它是怎么優(yōu)雅地關(guān)閉資源的!
package com.chloneda.jutils.test;
/**
* @author chloneda
* @description: close()方法測(cè)試
* 使用try-with-resources
*/
public class CloseTest {
public static void main(String[] args) throws ClassNotFoundException {
//1.加載驅(qū)動(dòng)程序
Class.forName("com.mysql.jdbc.Driver");
try (//2.獲得數(shù)據(jù)庫(kù)鏈接
Connection conn = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/common", "root", "123456");
//3.通過(guò)數(shù)據(jù)庫(kù)的連接操作數(shù)據(jù)庫(kù),實(shí)現(xiàn)增刪改查
Statement st = conn.createStatement();
ResultSet rs = st.executeQuery("select * from new_table")
) {
//4.處理數(shù)據(jù)庫(kù)的返回結(jié)果
while (rs.next()) {
System.out.println(rs.getString("id") + " " + rs.getString("name"));
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
這種方式就省略了finally,不必重復(fù)編寫(xiě)關(guān)閉資源的代碼了!而且資源也得到了關(guān)閉!怎么驗(yàn)證這個(gè)問(wèn)題?可以查看底下的 實(shí)際應(yīng)用 章節(jié)!
其實(shí)try-with-resources關(guān)閉資源的操作,本質(zhì)上是繼承了java.lang.AutoCloseable接口,實(shí)現(xiàn)了close方法,所以使用try-with-resources能關(guān)閉資源。很神奇吧!
實(shí)際應(yīng)用
這個(gè)章節(jié)就是驗(yàn)證使用try-with-resources可以關(guān)閉資源的問(wèn)題的!
上面我們說(shuō)了使用try-with-resources關(guān)閉資源,只要是繼承了java.lang.AutoCloseable接口 實(shí)現(xiàn)close()方法就可以使用!
我們自定義一個(gè)資源類(lèi),來(lái)看看實(shí)際應(yīng)用吧!
package com.chloneda.jutils.test;
/**
* @author chloneda
* @description: 資源類(lèi), 實(shí)現(xiàn)AutoCloseable接口.
*/
public class Resources implements AutoCloseable {
public void useResource() {
System.out.println("useResource:{} 正在使用資源!");
}
@Override
public void close() {
System.out.println("close:{} 自動(dòng)關(guān)閉資源!");
}
}
/**
* @description: 使用try-with-resources自動(dòng)關(guān)閉資源測(cè)試.
*/
class AutoClosableTest {
public static void main(String[] args) {
/** 使用try-with-resource,自動(dòng)關(guān)閉資源 */
try (
Resources resource = new Resources()
) {
resource.useResource();
} catch (Exception e) {
e.getMessage();
} finally {
System.out.println("Finally!");
}
}
}
結(jié)果輸出。
useResource:{} 正在使用資源!
close:{} 自動(dòng)關(guān)閉資源!
Finally!
看到運(yùn)行結(jié)果了嗎?Resources類(lèi)實(shí)現(xiàn)AutoCloseable接口,實(shí)現(xiàn)了close()方法,try-with-resources 就會(huì)自動(dòng)關(guān)閉資源!
一旦Resources類(lèi)沒(méi)有繼承java.lang.AutoCloseable接口,沒(méi)有實(shí)現(xiàn)close()方法,AutoClosableTest類(lèi)的try模塊就在編譯期報(bào)錯(cuò),提示信息如下。
Incompatible types.
Required: java.lang.AutoCloseable
Found: com.chloneda.jutils.test.Resources
最后,需要說(shuō)明的是try-with-resources就是一個(gè)JVM語(yǔ)法糖!關(guān)于JVM語(yǔ)法糖可以查查相關(guān)資料,看看Java中有哪些有趣的語(yǔ)法糖!
尾語(yǔ)
《Effective Java》在第三版中也推薦使用try-with-resources語(yǔ)句替代try-finally語(yǔ)句。
所以在處理必須關(guān)閉的資源時(shí),使用try-with-resources語(yǔ)句替代try-finally語(yǔ)句。生成的代碼更簡(jiǎn)潔,更清晰,并且生成的異常更有用。 try-with-resources語(yǔ)句在編寫(xiě)必須關(guān)閉資源的代碼時(shí)會(huì)更容易,也不會(huì)出錯(cuò),而使用try-finally語(yǔ)句實(shí)際上是不可能的。
如此,推薦大家使用try-with-resources優(yōu)雅地關(guān)閉資源!