try-with-resource:リソースを自動的に閉じる


一、資源閉鎖背景
Javaプログラミングの過程で、外部リソース(ファイル、データベース接続、ネットワーク接続など)を開いた場合、これらの外部リソースが使用された後、手動で閉じる必要があることを知っています.
外部リソースはJVMによって管理されていないため、JVMのゴミ回収メカニズムを利用することができません.プログラミング時に適切なタイミングで外部リソースを閉じることを確保しないと、外部リソースが漏洩し、ファイルが異常に占有され、データベース接続が多すぎて接続プールがオーバーフローするなど、深刻な問題が発生します.
 二、JDK 7以前の資源閉鎖方式
外部リソースが必ず閉じられるようにするには、通常、閉じたコードがfinallyコードブロックに書き込まれます.もちろん、リソースを閉じたときに投げ出される可能性のある異常にも注意しなければなりません.そこで、次の古典的なコードになります.
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);
            }
        }
    }
}

他の言語に詳しい友达はツッコミを入れるかもしれませんが、C++では、リソースを閉じるコードを構造関数に置くことができます.C#では、usingコードブロックがあります.これらの構文には、外部リソースのクローズ動作を外部リソースのハンドルオブジェクトのライフサイクルに関連付ける共通の特性があり、外部リソースのハンドルオブジェクトのライフサイクルが終了すると(ハンドルオブジェクトがアクティブになったドメインなど)、外部リソースのクローズ動作が自動的に呼び出されます.これにより,オブジェクト向けのプログラミングコンセプト(外部リソースを閉じる行為を外部リソースのハンドルオブジェクトに集約する)に適合するだけでなく,コードをより簡潔で分かりやすくする.どうしてJavaに着いたら、外部リソースを自動的に閉じる文法的特性が見つからないのでしょうか.
 三、JDK 7及びその後の資源閉鎖方式
3.1 try-with-resource構文
確かに、JDK 7以前はJavaは外部リソースの構文特性を自動的にオフにしていなかったが、JDK 7にtry-with-resource構文が追加されるまで、この機能を実現した.
try-with-resourceとは何ですか?
簡単に言えば、FileInputStreamオブジェクトなどの外部リソースのハンドルオブジェクトに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);
    }
}

外部リソースのハンドルオブジェクトの作成をtryキーの後ろのカッコに入れます.このtry-catchコードブロックが実行されると、Javaは外部リソースのcloseメソッドが呼び出されることを保証します.コードは一瞬で簡潔になったのではないでしょうか.
 3.2実現原理
try-with-resourceはJVM仮想マシンの新機能ではありません.ただ、JDKは文法糖を実現しています.上のコードを逆コンパイルすると、JVM仮想マシンにとって、以前の書き方が見えます.
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);
    }
}

 
 3.3異常抑制
逆コンパイルされたコードによって、コードに異常に対する特殊な処理があることに気づくかもしれません.
var2.addSuppressed(var11);

これはtry-with-resource構文に関連するもう一つの知識点であり、異常抑制と呼ばれている.外部リソースの処理(読み取りや書き込みなど)を行う場合、異常に遭遇し、その後の外部リソースのクローズ中に異常に遭遇した場合、catchは外部リソースを処理する際に遭遇する異常であり、リソースをクローズする際に遭遇する異常は「抑制」されますが、破棄されません.異常なgetSuppressedメソッドにより、抑制された異常を抽出することができる.
 3.4 try-with-resources文で1つ以上のリソースを宣言try-with-resources文で1つ以上のリソースを宣言できます.次の例では、zipファイルでパッケージ化されたファイルの名前を取得し、zipFileNameでファイル名を含むテキストファイルを作成します.
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文には、ZipFileBufferedWriterの2つの宣言がセミコロンで区切られています.
コードブロックが正常または異常によって終了すると、  BufferedWriter  および  ZipFile  オブジェクトのcloseメソッド順序が自動的に呼び出されます.
リソースのクローズ方法は、作成された逆の順序で呼び出されることに注意してください.
次の例では、try-with-resources文を使用して、java.sql.Statementオブジェクトを自動的に閉じます.
public static void viewTable(Connection con) throws SQLException {

    String query = "select COF_NAME, SUP_ID, PRICE, SALES, TOTAL from COFFEES";

    try (Statement stmt = con.createStatement()) {
        ResultSet rs = stmt.executeQuery(query);

        while (rs.next()) {
            String coffeeName = rs.getString("COF_NAME");
            int supplierID = rs.getInt("SUP_ID");
            float price = rs.getFloat("PRICE");
            int sales = rs.getInt("SALES");
            int total = rs.getInt("TOTAL");

            System.out.println(coffeeName + ", " + supplierID + ", " + 
                               price + ", " + sales + ", " + total);
        }
    } catch (SQLException e) {
        JDBCTutorialUtilities.printSQLException(e);
    }
}
java.sql.Statementこの例で使用されるリソースは、JDBC 4.1以降のAPIの一部である.
注意:
try-with-resources文は、通常のtry文のようにcatchおよびfinally を有することができる.try-with-resources文では、宣言されたリソースが閉じた後に、catchまたはfinallyブロックが実行されます.
四、まとめ
1、外部リソースのハンドルオブジェクトがAutoCloseableインタフェースを実現すると、JDK 7でtry-with-resource構文を利用してより優雅にリソースを閉じ、ボードコードを除去することができる.
2、try-with-resourceの場合、外部リソースの処理と外部リソースのクローズに異常が発生した場合、「クローズ異常」は抑制され、「処理異常」は投げ出されるが、「クローズ異常」は失われず、「処理異常」の抑制された異常リストに格納される.
参照リンク:https://docs.oracle.com/javase/tutorial/essential/exceptions/tryResourceClose.html