try with resoure自動(dòng)關(guān)閉資源

背景

我們知道,在Java編程過程中,如果打開了外部資源(文件、數(shù)據(jù)庫連接、網(wǎng)絡(luò)連接等),我們必須在這些外部資源使用完畢后,手動(dòng)關(guān)閉它們。

因?yàn)橥獠抠Y源不由JVM管理,無法享用JVM的垃圾回收機(jī)制,如果我們不在編程時(shí)確保在正確的時(shí)機(jī)關(guān)閉外部資源,就會(huì)導(dǎo)致外部資源泄露,緊接著就會(huì)出現(xiàn)文件被異常占用,數(shù)據(jù)庫連接過多導(dǎo)致連接池溢出等諸多很嚴(yán)重的問題。

JDK7之前的資源關(guān)閉方式

為了確保外部資源一定要被關(guān)閉,通常關(guān)閉代碼被寫入finally代碼塊中,當(dāng)然我們還必須注意到關(guān)閉資源時(shí)可能拋出的異常,于是變有了下面的經(jīng)典代碼:

public static void main(String[] args) {
    FileInputStream inputStream = null;
    try {
        inputStream = new FileInputStream(new File("test"));
        System.out.println(inputStream.read());
    } catch (IOException e) {
        throw new RuntimeException(e.getMessage(), e);
    } finally {
        if (inputStream != null) {
            try {
                inputStream.close();
            } catch (IOException e) {
                throw new RuntimeException(e.getMessage(), e);
            }
        }
    }
}

熟悉其他語言的朋友可能會(huì)開始吐槽了,在C++中,我們可以把關(guān)閉資源的代碼放在析構(gòu)函數(shù)中,在C#中,我們有using代碼塊。這些語法都有一個(gè)共同的特性,讓外部資源的關(guān)閉行為與外部資源的句柄對象的生命周期關(guān)聯(lián),當(dāng)外部資源的句柄對象生命周期終結(jié)時(shí)(例如句柄對象已出作用域),外部資源的關(guān)閉行為將被自動(dòng)調(diào)用。這樣不僅更加符合面向?qū)ο蟮木幊汤砟睿▽㈥P(guān)閉外部資源的行為內(nèi)聚在外部資源的句柄對象中),也讓代碼更加簡潔易懂。怎么到了Java這里,就找不到自動(dòng)關(guān)閉外部資源的語法特性了呢。

JDK7及其之后的資源關(guān)閉方式

try-with-resource語法

確實(shí),在JDK7以前,Java沒有自動(dòng)關(guān)閉外部資源的語法特性,直到JDK7中新增了try-with-resource語法,才實(shí)現(xiàn)了這一功能。

那什么是try-with-resource呢?

簡而言之,當(dāng)一個(gè)外部資源的句柄對象(比如FileInputStream對象)實(shí)現(xiàn)了AutoCloseable接口,
那么就可以將上面的板式代碼簡化為如下形式:

public static void main(String[] args) {
    try (FileInputStream inputStream = new FileInputStream(new File("test"))) {
        System.out.println(inputStream.read());
    } catch (IOException e) {
        throw new RuntimeException(e.getMessage(), e);
    }
}

將外部資源的句柄對象的創(chuàng)建放在try關(guān)鍵字后面的括號(hào)中,當(dāng)這個(gè)try-catch代碼塊執(zhí)行完畢后,Java會(huì)確保外部資源的close方法被調(diào)用。代碼是不是瞬間簡潔許多!

實(shí)現(xiàn)原理

try-with-resource并不是JVM虛擬機(jī)的新增功能,只是JDK實(shí)現(xiàn)了一個(gè)語法糖,當(dāng)你將上面代碼反編譯后會(huì)發(fā)現(xiàn),其實(shí)對JVM虛擬機(jī)而言,它看到的依然是之前的寫法。

public static void main(String[] args) {
    try {
        FileInputStream inputStream = new FileInputStream(new File("test"));
        Throwable var2 = null;
 
        try {
            System.out.println(inputStream.read());
        } catch (Throwable var12) {
            var2 = var12;
            throw var12;
        } finally {
            if (inputStream != null) {
                if (var2 != null) {
                    try {
                        inputStream.close();
                    } catch (Throwable var11) {
                        var2.addSuppressed(var11);
                    }
                } else {
                    inputStream.close();
                }
            }
 
        }
 
    } catch (IOException var14) {
        throw new RuntimeException(var14.getMessage(), var14);
    }
}

異常抑制

通過反編譯的代碼,大家可能注意到代碼中有一處對異常的特殊處理:

var2.addSuppressed(var11);

這是try-with-resource語法涉及的另外一個(gè)知識(shí)點(diǎn),叫做異常抑制。當(dāng)對外部資源進(jìn)行處理(例如讀或?qū)懀r(shí),如果遭遇了異常,且在隨后的關(guān)閉外部資源過程中,又遭遇了異常,那么你catch到的將會(huì)是對外部資源進(jìn)行處理時(shí)遭遇的異常,關(guān)閉資源時(shí)遭遇的異常將被“抑制”但不是丟棄,通過異常的getSuppressed方法,可以提取出被抑制的異常。

try-with-resources語句中聲明一個(gè)或多個(gè)資源

你可以在try-with-resources語句中聲明一個(gè)或多個(gè)資源。以下示例檢索zip文件中打包的文件的名稱,zipFileName并創(chuàng)建包含這些文件名稱的文本文件:

public static void writeToFileZipFileContents(String zipFileName,
                                           String outputFileName)
                                           throws java.io.IOException {
 
    java.nio.charset.Charset charset =
         java.nio.charset.StandardCharsets.US_ASCII;
    java.nio.file.Path outputFilePath =
         java.nio.file.Paths.get(outputFileName);
 
    // Open zip file and create output file with 
    // try-with-resources statement
 
    try (
        java.util.zip.ZipFile zf =
             new java.util.zip.ZipFile(zipFileName);
        java.io.BufferedWriter writer = 
            java.nio.file.Files.newBufferedWriter(outputFilePath, charset)
    ) {
        // Enumerate each entry
        for (java.util.Enumeration entries =
                                zf.entries(); entries.hasMoreElements();) {
            // Get the entry name and write it to the output file
            String newLine = System.getProperty("line.separator");
            String zipEntryName =
                 ((java.util.zip.ZipEntry)entries.nextElement()).getName() +
                 newLine;
            writer.write(zipEntryName, 0, zipEntryName.length());
        }
    }
}

In this example, the try-with-resources statement contains two declarations that are separated by a semicolon: ZipFile and BufferedWriter. When the block of code that directly follows it terminates, either normally or because of an exception, the close methods of the BufferedWriter and ZipFile objects are automatically called in this order. Note that the close methods of resources are called in the opposite order of their creation.

在此示例中,try-with-resources語句包含以分號(hào)分隔的兩個(gè)聲明:ZipFile和BufferedWriter。

當(dāng)直接跟隨它的代碼塊正常或由于異常而終止時(shí),將按 BufferedWriter 和 ZipFile 對象的close方法順序自動(dòng)調(diào)用。

請注意,資源的關(guān)閉方法按其創(chuàng)建的相反順序調(diào)用。

小結(jié)

1、當(dāng)一個(gè)外部資源的句柄對象實(shí)現(xiàn)了AutoCloseable接口,JDK7中便可以利用try-with-resource語法更優(yōu)雅的關(guān)閉資源,消除板式代碼。

2、try-with-resource時(shí),如果對外部資源的處理和對外部資源的關(guān)閉均遭遇了異常,“關(guān)閉異常”將被抑制,“處理異常”將被拋出,但“關(guān)閉異常”并沒有丟失,而是存放在“處理異常”的被抑制的異常列表中。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容