179

Java 7 のtry-with-resources構文 (ARM ブロック ( Automatic Resource Management ) とも呼ばれます) は、AutoCloseableリソースを 1 つだけ使用する場合に便利で、短くて簡単です。FileWriterただし、相互に依存する複数のリソースを宣言する必要がある場合、たとえば aと aBufferedWriterをラップする場合、どのイディオムが正しいかわかりません。もちろん、この質問は、AutoCloseableこれら 2 つの特定のクラスだけでなく、一部のリソースがラップされている場合にも関係します。

私は次の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のサブタイプであるAutoCloseable両方を実装するこれら 2 つの特定のクラスでは、これは問題にはなりませんclose

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

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

java.io.Closeable の close メソッドとは異なり、この close メソッドはべき等である必要がないことに注意してください。つまり、この close メソッドを複数回呼び出すと、目に見える副作用が生じる可能性があります。これは、複数回呼び出されても効果がないことが必要な Closeable.close とは異なります。ただし、このインターフェイスの実装者は、close メソッドを冪等にすることを強くお勧めします。

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
    }
}

fwクリーンアップが必要な実際のリソースを表すのはのみであるため、このバージョンは理論的に正しいはずです。自体はbwリソースを保持せず、 に委譲するfwだけなので、基になる を閉じるだけで十分fwです。

一方、構文は少し不規則であり、Eclipse は警告を発行します。これは誤報だと思いますが、それでも対処しなければならない警告です。

リソース リーク: 'bw' は決して閉じられません


では、どのアプローチを採用すればよいでしょうか。それとも、正しいものである他のイディオムを見逃したことがありますか?

4

8 に答える 8

80

代替案についての私の見解は次のとおりです。

1)

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

私にとって、15 年前に従来の C++ から Java に移行して一番よかったのは、自分のプログラムを信頼できることでした。よくあることですが、物事がうまくいかずうまくいかない場合でも、コードの残りの部分は最善の動作をしてバラの匂いがするようにしたいと考えています。実際、BufferedWriterここで例外がスローされる可能性があります。たとえば、メモリ不足は珍しいことではありません。他のデコレーターについては、どのjava.ioラッパー クラスがコンストラクターからチェック例外をスローするか知っていますか? 私はしません。そのようなあいまいな知識に依存している場合、コードの理解度はあまり良くありません。

「破壊」もあります。エラー状態が発生した場合、削除が必要なファイルにゴミをフラッシュしたくないでしょう (そのためのコードは示されていません)。もちろん、ファイルを削除することも、エラー処理として興味深い操作です。

一般に、finallyブロックはできるだけ短く、信頼できるものにする必要があります。フラッシュを追加しても、この目標には役立ちません。多くのリリースでは、JDK のバッファリング クラスの一部に、装飾されたオブジェクトで発生したflush内部からの例外が呼び出されないというバグがありました。これはしばらくの間修正されていますが、他の実装に期待してください。closeclose

2)

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

暗黙のfinallyブロックでまだフラッシュしています(現在は繰り返さcloseれています-デコレーターを追加するとこれは悪化します)が、構築は安全であり、失敗してもflushリソースの解放を妨げないように暗黙のfinallyブロックを使用する必要があります。

3)

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

ここにバグがあります。次のようにする必要があります。

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

不完全に実装されたデコレータの中には、実際にはリソースであり、確実に閉じる必要があるものがあります。また、一部のストリームは特定の方法で閉じる必要がある場合があります (おそらく、ストリームは圧縮を行っており、終了するためにビットを書き込む必要があり、すべてをフラッシュすることはできません。

評決

3 は技術的に優れたソリューションですが、ソフトウェア開発上の理由から 2 の方が適しています。ただし、try-with-resource はまだ不十分な修正であり、Java SE 8 のクロージャーを使用したより明確な構文を持つ必要があるExecute Around イディオムを使用する必要があります。

于 2012-09-30T21:27:58.083 に答える
22

最初のスタイルは、Oracle によって提案されたものです。BufferedWriterチェックされた例外をスローしないため、例外がスローされた場合、プログラムはそれから回復することが期待されず、リソースの回復はほとんど意味がありません。

主な理由は、スレッドが停止してもプログラムがまだ継続しているスレッドで発生する可能性があるためです。たとえば、プログラムの残りの部分に深刻な影響を与えるほど長くはなかった一時的なメモリの停止がありました。ただし、これはかなりまれなケースであり、リソース リークが問題になるほど頻繁に発生する場合は、try-with-resources が問題になることはほとんどありません。

于 2013-08-28T19:18:32.063 に答える
5

オプション 4

可能であれば、リソースを AutoClosable ではなく Closeable に変更してください。コンストラクターを連鎖できるという事実は、リソースを 2 回閉じるという前代未聞ではないことを意味します。(これは ARM の前にも当てはまりました。) これについては以下で詳しく説明します。

オプション 5

close() が 2 回呼び出されないように、ARM とコードを慎重に使用しないでください。

オプション 6

ARM を使用せず、try/catch 自体で finally close() 呼び出しを行います。

この問題が ARM に固有のものではないと思う理由

これらすべての例で、finally close() 呼び出しは catch ブロックにある必要があります。読みやすくするために省略します。

FWは2回閉じられるからダメ。(これは FileWriter では問題ありませんが、架空の例では問題ありません):

FileWriter fw = null;
BufferedWriter bw = null;
try {
  fw = new FileWriter(file);
  bw = new BufferedWriter(fw);
  bw.write(text);
} finally {
  if ( fw != null ) fw.close();
  if ( bw != null ) bw.close();
}

BufferedWriter の構築時に例外が発生した場合、fw が閉じられないため、ダメです。(繰り返しますが、起こり得ませんが、架空の例では):

FileWriter fw = null;
BufferedWriter bw = null;
try {
  fw = new FileWriter(file);
  bw = new BufferedWriter(fw);
  bw.write(text);
} finally {
  if ( bw != null ) bw.close();
}
于 2012-09-23T18:01:17.730 に答える
2

リソースはネストされているため、try-with句も次のようになります。

try (FileWriter fw=new FileWriter(file)) {
    try (BufferedWriter bw=new BufferedWriter(fw)) {
        bw.write(text);
    } catch (IOException ex) {
        // handle ex
    }
} catch (IOException ex) {
    // handle ex
}
于 2012-09-26T15:28:48.557 に答える
2

ARM を使用せず、FileWriter が常に 1 回だけ閉じられるようにするという Jeanne Boyarsky の提案に基づいて構築したかっただけです。ここに問題があるとは思わないでください...

FileWriter fw = null;
BufferedWriter bw = null;
try {
    fw = new FileWriter(file);
    bw = new BufferedWriter(fw);
    bw.write(text);
} finally {
    if (bw != null) bw.close();
    else if (fw != null) fw.close();
}

ARM は単なるシンタックス シュガーであるため、finally ブロックを置き換えるために常に使用できるとは限りません。for-each ループを常に使用して、反復子で可能なことを実行できるとは限らないのと同じように。

于 2012-09-30T21:33:20.120 に答える
0

ARM を使用せず、Closeable を使用することをお勧めします。次のような方法を使用します。

public void close(Closeable... closeables) {
    for (Closeable closeable: closeables) {
       try {
           closeable.close();
         } catch (IOException e) {
           // you can't much for this
          }
    }

}

BufferedWriterまた、 close を に委任するだけでなく、 のFileWriterようなクリーンアップも行うため、 closeの呼び出しを検討する必要がありflushBufferます。

于 2012-09-27T10:58:38.777 に答える