try-with-resources ブロックで複数の連鎖リソースを管理するための正しい慣用句は何ですか? 質問する

try-with-resources ブロックで複数の連鎖リソースを管理するための正しい慣用句は何ですか? 質問する

Java 7はリソースを試す構文(ARMブロックとも呼ばれる)自動リソース管理)) は、リソースを 1 つだけ使用する場合は、簡潔でわかりやすい便利な表現ですAutoCloseable。ただし、 とそれをラップする など、相互に依存する複数のリソースを宣言する必要がある場合、正しい表現が何であるかわかりませんFileWriter。もちろん、この質問は、これらの 2 つの特定のクラスだけでなく、いくつかのリソースがラップされるBufferedWriterすべてのケースに関係します。AutoCloseable

私は次の3つの選択肢を思いつきました。

1)

私が見た素朴な慣用句は、ARM 管理変数でトップレベルのラッパーのみを宣言することです。

static void printToFile1(String text, File file) {
    try (BufferedWriter bw = new BufferedWriter(new FileWriter(file))) {
        bw.write(text);
    } catch (IOException ex) {
        // handle ex
    }
}

これは短くて良いですが、壊れています。基になる はFileWriter変数で宣言されていないため、生成されたブロックで直接閉じられることはありません。ラップ のメソッドfinallyを通じてのみ閉じられます。問題は、のコンストラクタから例外がスローされた場合、 は呼び出されず、基になる が呼び出されないことです。closeBufferedWriterbwcloseFileWriter 閉鎖されない

2)

static void printToFile2(String text, File file) {
    try (FileWriter fw = new FileWriter(file);
            BufferedWriter bw = new BufferedWriter(fw)) {
        bw.write(text);
    } catch (IOException ex) {
        // handle ex
    }
}

ここでは、基になるリソースとラッピングリソースの両方がARM管理変数で宣言されているため、両方とも確実に閉じられますが、基になるfw.close() 2回呼び出されます: 直接だけでなく、ラッピングを通してもbw.close()

Closeableこれは、 ( のサブタイプ)を実装する次の 2 つの特定のクラスでは問題にならないはずです。AutoCloseableこれらのクラスの規約では、 を複数回呼び出すことがclose許可されています。

このストリームを閉じ、それに関連付けられているすべてのシステム リソースを解放します。ストリームがすでに閉じられている場合、このメソッドを呼び出しても効果はありません。

ただし、一般的なケースでは、 のみを実装するAutoCloseable( は実装しない)リソースを持つことができ、 が複数回呼び出されるCloseableことは保証されません。close

Note that unlike the close method of java.io.Closeable, this close method is not required to be idempotent. In other words, calling this close method more than once may have some visible side effect, unlike Closeable.close which is required to have no effect if called more than once. However, implementers of this interface are strongly encouraged to make their close methods idempotent.

3)

static void printToFile3(String text, File file) {
    try (FileWriter fw = new FileWriter(file)) {
        BufferedWriter bw = new BufferedWriter(fw);
        bw.write(text);
    } catch (IOException ex) {
        // handle ex
    }
}

This version should be theoretically correct, because only the fw represents a real resource that needs to be cleaned up. The bw doesn't itself hold any resource, it only delegates to the fw, so it should be sufficient to only close the underlying fw.

On the other hand, the syntax is a bit irregular and also, Eclipse issues a warning, which I believe is a false alarm, but it is still a warning that one has to deal with:

Resource leak: 'bw' is never closed


So, which approach to go for? Or have I missed some other idiom that is the correct one?

ベストアンサー1

Here's my take on the alternatives:

1)

try (BufferedWriter bw = new BufferedWriter(new FileWriter(file))) {
    bw.write(text);
}

For me, the best thing coming to Java from traditional C++ 15 years ago was that you could trust your program. Even if things are in the muck and going wrong, which they often do, I want the rest of the code to be on best behaviour and smelling of roses. Indeed, the BufferedWriter might throw an exception here. Running out of memory wouldn't be unusual, for instance. For other decorators, do you know which of the java.io wrapper classes throw a checked exception from their constructors? I don't. Doesn't do code understandability much good if you rely upon that sort of obscure knowledge.

Also there's the "destruction". If there is an error condition, then you probably don't want to be flushing rubbish to a file that needs deleting (code for that not shown). Although, of course, deleting the file is also another interesting operation to do as error handling.

Generally you want finally blocks to be as short and reliable as possible. Adding flushes does not help this goal. For many releases some of the buffering classes in the JDK had a bug where an exception from flush within close caused close on the decorated object not be called. Whilst that has been fixed for some time, expect it from other implementations.

2)

try (
    FileWriter fw = new FileWriter(file);
    BufferedWriter bw = new BufferedWriter(fw)
) {
    bw.write(text);
}

We're still flushing in the implicit finally block (now with repeated close - this gets worse as you add more decorators), but the construction is safe and we have two implicit finally blocks so even a failed flush doesn't prevent resource release.

3)

try (FileWriter fw = new FileWriter(file)) {
    BufferedWriter bw = new BufferedWriter(fw);
    bw.write(text);
}

There's a bug here. Should be:

try (FileWriter fw = new FileWriter(file)) {
    BufferedWriter bw = new BufferedWriter(fw);
    bw.write(text);
    bw.flush();
}

Some poorly implemented decorators are in fact resource and will need to be closed reliably. Also some streams may need to be closed in a particular way (perhaps they are doing compression and need to write bits to finish off, and can't just flush everything.

Verdict

Although 3 is a technically superior solution, software development reasons make 2 the better choice. However, try-with-resource is still an inadequate fix and you should stick with the Execute Around idiom, which should have a clearer syntax with closures in Java SE 8.

おすすめ記事