Java 如何優(yōu)雅地使用close()

注:本文出自博主 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)閉資源!


最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容