web-dev-qa-db-ja.com

AutoCloseableで複数のリソースを閉じる(try-with-resources)

リソースにAutoCloseableが実装されている場合、試行で渡すリソースは自動的に閉じられることを知っています。ここまでは順調ですね。しかし、自動的に閉じたいリソースがいくつかある場合はどうすればよいですか。ソケットの例;

try (Socket socket = new Socket()) {
    input = new DataInputStream(socket.getInputStream());
    output = new DataOutputStream(socket.getOutputStream());
} catch (IOException e) {
} 

ソケットは、tryのパラメーターとして渡されるため、適切に閉じられることはわかっていますが、入出力をどのように適切に閉じる必要がありますか?

53
asmb

リソースを試してみてください。複数のリソースをすべてかっこで宣言することで使用できます。 ドキュメント を参照してください

リンクされたドキュメントからの関連コードの抜粋:

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());
        }
    }
}

オブジェクトがAutoClosableを実装していない場合(DataInputStreamが実装している場合)、またはtry-with-resourcesの前に宣言する必要がある場合、それらを閉じる適切な場所はfinallyブロック内です。リンクされたドキュメントにも記載されています。

67
augray

心配しないでください、物事は「うまくいく」でしょう。 Socketのドキュメント から:

このソケットを閉じると、ソケットのInputStreamとOutputStreamも閉じます。

入力オブジェクトと出力オブジェクトでclose()を明示的に呼び出さないことに関する懸念を理解しています。実際、次のように、すべてのリソースがtry-with-resourcesブロックによって自動的に管理されるようにする方が一般的には良いです:

try (Socket socket = new Socket();
     InputStream input = new DataInputStream(socket.getInputStream());
     OutputStream output = new DataOutputStream(socket.getOutputStream());) {
} catch (IOException e) {
} 

これにより、ソケットオブジェクトが「複数回閉じられる」という効果がありますが、害はありません(これは、close()のすべての実装をべき等にすることが一般的に推奨される理由の1つです)。

21
Paulo

上記の回答に加えて、これはJava 9で追加された改善です。

Java 9 try-with-resourcesは、コードの記述方法を改善します。これで、tryブロックの外で変数を宣言し、tryブロック内で直接使用できます。このため、次の利点が得られます。

  • Try(事実上最終または最終)の外で宣言したリソースは、tryブロックに追加するだけで、自動リソース管理によって自動的に閉じることができます。
    • Java 7で行う必要があるように、tryブロックの外で宣言されたオブジェクトを再参照する必要も、手動で閉じる必要もありません。
    • また、きれいなコードを書くのに役立ちます。

try-with-resourceをJava 9にこのように書くことができます。

public void loadDataFromDB() throws SQLException {
Connection dbCon = DriverManager.getConnection("url", "user", "password");
try (dbCon; ResultSet rs = dbCon.createStatement().executeQuery("select * from emp")) {
    while (rs.next()) {
        System.out.println("In loadDataFromDB() =====>>>>>>>>>>>> " + rs.getString(1));
    }
} catch (SQLException e) {
    System.out.println("Exception occurs while reading the data from DB ->" + e.getMessage());
}

}

ここで、自動リソース管理はdbConとrsの両方のオブジェクトを自動的に閉じます。

上記の定義ユースケースのリストをよりよく理解するために、いくつかのJava 7コードを見つけてください。

例1:

public void loadDataFromDB() throws SQLException {
Connection dbCon = DriverManager.getConnection("url", "user", "password");
try (ResultSet rs = dbCon.createStatement().executeQuery("select * from emp")) {
    while (rs.next()) {
        System.out.println("In loadDataFromDB() =====>>>>>>>>>>>> " + rs.getString(1));
    }
} catch (SQLException e) {
    System.out.println("Exception occurs while reading the data from DB ->" + e.getMessage());
} finally {
    if (null != dbCon)
        dbCon.close();
}

}

例2:

// BufferedReader is declared outside try() block
    BufferedReader br = new BufferedReader(new FileReader("C://readfile/input.txt"));

    try (BufferedReader inBr = br) {
            // ...
        }
    } catch (IOException e) {
        // ...
    }

上記のサンプルでは、​​オブジェクトが終了しているかどうかを確認できます。手動で閉じるか、再参照する必要があります。また、tryブロック内の複数のオブジェクトの場合、面倒に見え、try内で宣言した場合でも、tryブロック外で使用することはできません。

3
Shivang Agarwal

上記の回答は素晴らしいですが、try-with-resourcesが役に立たない場合があります。

このコード例を見てください:

private static byte[] getFileBytes(Collection<String> fileContent) throws CustomServiceException {
    try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
        try (BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(baos))) {
            for (String fileLine : fileContent) {
                writer.append(fileLine);
                writer.newLine();
            }
        }
        return baos.toByteArray();
    } catch (IOException e) {
        throw new CustomServiceException(SC_INTERNAL_SERVER_ERROR, "Unable to serialize file data.");
    }
}

この例では、try-with-resourcesブロックを使用することはできません。ライターは出力バッファーを基になる文字ストリームにフラッシュする必要があるため、writerをtry-with-resourcesブロックに配置してもトリックは実行されず、メソッドは空の配列を返します。

1
Dzmitry Hubin