リソースの誤った解放は、Javaプログラマーの間で最もよく発生するエラーの1つです。 この記事のリソースでは、
java.io.Closeableインターフェースを実装するすべてのものを意味します。 だから、ポイントに右。
OutputStreamの例を検討します。 タスク:入力への
OutputStreamを取得し、いくつかの有用な作業を行い、
OutputStream閉じます。
間違った決定No. 1
OutputStream stream = openOutputStream();
コードで例外がスローされた場合、stream.close()は呼び出されないため、このソリューションは危険です。 リソースリークが発生します(接続が閉じられない、ファイル記述子が解放されない、など)
間違った決定No. 2
前のコードを修正してみましょう。
try-finallyを使用します。
OutputStream stream = openOutputStream(); try {
現在、
close()は常に呼び出されます(
finally )ため:リソースはとにかく解放されます。 すべてが正しいようです。 そうですか?
いや
問題は次のとおりです。
close()メソッドは例外をスローする場合があります。 また、リソースを操作するためのメインコードも例外をスローする場合、
close()からの例外によってオーバーランされます。 元のエラーに関する情報は失われます。元の例外の原因はわかりません。
間違った決定番号3
状況を修正してみましょう。
stream.close()が「メイン」例外をキャッチできる場合、
close()から例外を「飲み込み」ましょう。
OutputStream stream = openOutputStream(); try {
今、すべてがうまくいくようです。 私たちはお茶を飲みに行くことができます。
どんなに。 このソリューションは、前のソリューションよりもさらに悪いです。 なんで?
なぜなら、私たちは
close()から例外を取得して飲み込んだからです。
outputStreamが
BufferedOutputStreamラップされた
FileOutputStreamとし
outputStreamう。
BufferedOutputStreamは、基礎となるストリームをチャンクで
flush()するため、
close()呼び出し中に呼び出す可能性があります。 ここで、書き込み先のファイルがロックされていると想像してください。 その後、
close()メソッドは
IOExceptionをスローし、これは正常に食べられます。 ユーザーデータの1バイトはファイルに書き込まれず、それについて何も学習しませんでした。 情報が失われました。
このソリューションを前のソリューションと比較すると、少なくとも何か悪いことが起こったことがわかります。 ここでは、すべてのエラー情報が消えます。
注: OutputStream代わりにInputStream使用されている場合、このコードには生存権があります。 事実は、例外がInputStream.close()でスローされた場合、このストリームから必要なものをすべて考慮したため、(ほとんどの場合)悪い結果はありません。 これは、 InputStreamとOutputStreamセマンティクスがまったく異なることを意味します。不完全なソリューション
では、リソース処理コードはどのように見えるのでしょうか?
メインコードが例外をスローする場合、この例外は
close()メソッドでスローできるものよりも優先される必要があることを考慮する必要があります。 次のようになります。
OutputStream stream = openOutputStream(); Throwable mainThrowable = null; try {
この決定の欠点は明らかです。面倒で難しいです。 また、メインコードが例外をスローすると、
close()からの例外に関する情報
close()失われます。 また、
openOutputStream()は
nullを返すことがあり、その後
NullPointerExceptionが
NullPointerExceptionされます(別のifを追加することで解決され、さらに面倒なコードになります)。 最後に、2つのリソース(
InputStreamと
OutputStream )がある場合、コードは単純に耐えられないほど複雑になります。
適切なソリューション(Java 7)
Java 7では、
try-with-resourcesコンストラクトが導入されました。 私たちはそれを使用します:
try (OutputStream stream = openOutputStream()) {
それだけです。
メインコードと
close()メソッドで例外がスローされた場合、最初の例外が優先され、2番目の例外が抑制されますが、その情報は保存されます(Javaコンパイラによって暗黙的に呼び出される
Throwable.addSuppressed(Throwable exception)メソッドを使用):
Exception in thread "main" java.lang.RuntimeException: Main exception at A$1.write(A.java:16) at A.doSomething(A.java:27) at A.main(A.java:8) Suppressed: java.lang.RuntimeException: Exception on close() at A$1.close(A.java:21) at A.main(A.java:9)
適切なソリューション(Google Guavaを使用したJava 6)
Java 6では、標準ライブラリだけでは対応できません。 しかし、素晴らしい
Google Guavaライブラリが私たちの助けになります。 クラス
com.google.common.io.Closer (貧しい人々のための
try-with-resources )がGuava 14.0に登場しました。これにより、上記の不完全なソリューションを大幅に簡素化できます。
Closer closer = Closer.create(); try { OutputStream stream = closer.register(openOutputStream());
このソリューションは、Java 7の場合よりも著しく長くなりますが、不完全なソリューションよりもはるかに短くなります。 出力はJava 7とほぼ同じになります。
Closerは、その中の任意の量のリソースもサポートします(
register(...)メソッド)。 残念ながら、
Closerは
@Betaアノテーションでマークされたクラスです。つまり、ライブラリの将来のバージョンで大幅に変更される可能性があります(削除まで)。
結論
リソースを正しく解放するのは、見た目ほど簡単ではありません(Java 7のみ)。 これには常に十分な注意を払ってください。
InputStreamとOutputStream ( ReaderとWriter )の処理は異なります(少なくともJava 6では)。追加/修正は大歓迎です!
次回は
NullPointerException処理方法を説明する予定です。