最終的にブロックで例外をスローします


100

finallyブロックでスローされる例外を処理するエレガントな方法はありますか?

例えば:

try {
  // Use the resource.
}
catch( Exception ex ) {
  // Problem with the resource.
}
finally {
   try{
     resource.close();
   }
   catch( Exception ex ) {
     // Could not close the resource?
   }
}

ブロック内のtry/をどのように回避しますか?catchfinally

回答:


72

私は通常、次のようにします。

try {
  // Use the resource.
} catch( Exception ex ) {
  // Problem with the resource.
} finally {
  // Put away the resource.
  closeQuietly( resource );
}

その他:

protected void closeQuietly( Resource resource ) {
  try {
    if (resource != null) {
      resource.close();
    }
  } catch( Exception ex ) {
    log( "Exception during Resource.close()", ex );
  }
}

4
うん、私は非常に似たイディオムを使用しています。しかし、そのための関数は作成しません。
OscarRyz 2009年

9
関数は、同じクラスの数か所でイディオムを使用する必要がある場合に便利です。
ダロン

nullのチェックは冗長です。リソースがnullの場合、呼び出しメソッドが壊れているのを修正する必要があります。また、リソースがnullの場合、おそらくログに記録されます。それ以外の場合は、潜在的に例外が暗黙的に無視されます。
デイブジャービス

14
nullのチェックは必ずしも冗長ではありません。「resource = new FileInputStream( "file.txt")」は、最初の行と考えてください。また、この質問は、多くの人が使用しないアスペクト指向プログラミングに関するものではありませんでした。ただし、例外を単に無視するべきではないという概念は、ログステートメントを表示することで最もコンパクトに処理されました。
Darron、

1
Resource=> Closeable
ドミトリーギン

25

私は通常、次のいずれかのcloseQuietly方法を使用しますorg.apache.commons.io.IOUtils

public static void closeQuietly(OutputStream output) {
    try {
        if (output != null) {
            output.close();
        }
    } catch (IOException ioe) {
        // ignore
    }
}

3
Closeable public static void closeQuietly(Closeable closeable){
Peter Lawrey

6
はい、Closeableは素晴らしいです。多くのこと(JDBCリソースなど)が実装していないのは残念です。
ダロン、

22

Java 7を使用していて、をresource実装AutoClosableしている場合は、これを行うことができます(例としてInputStreamを使用)。

try (InputStream resource = getInputStream()) {
  // Use the resource.
}
catch( Exception ex ) {
  // Problem with the resource.
}

8

おそらく少し上ですが、例外をバブルアップさせてメソッド内から何もログに記録できない場合に役立つかもしれません(たとえば、それがライブラリであり、呼び出しコードに例外とロギングを処理させたい場合)。

Resource resource = null;
boolean isSuccess = false;
try {
    resource = Resource.create();
    resource.use();
    // Following line will only run if nothing above threw an exception.
    isSuccess = true;
} finally {
    if (resource != null) {
        if (isSuccess) {
            // let close throw the exception so it isn't swallowed.
            resource.close();
        } else {
            try {
                resource.close();
            } catch (ResourceException ignore) {
                // Just swallow this one because you don't want it 
                // to replace the one that came first (thrown above).
            }
        }
    }
}

更新:私はこれをもう少し調べて、私よりもこれについて明確に考えている誰かからの素晴らしいブログ投稿を見つけました:http : //illegalargumentexception.blogspot.com/2008/10/java-how-not-to-make -mess-of-stream.html 彼はさらに一歩進んで、2つの例外を1つに結合しました。


1
ブログリンクの+1。さらに、少なくともignore例外をログに記録します
Denis Kniazhev

6

Java 7以降では、finallyブロックでリソースを明示的に閉じる必要がなくなり、代わりにtry -with-resources構文を使用できます。try-with-resourcesステートメントは、1つ以上のリソースを宣言するtryステートメントです。リソースは、プログラムが終了した後に閉じる必要があるオブジェクトです。try-with-resourcesステートメントは、各リソースがステートメントの最後で確実に閉じられるようにします。java.io.Closeableを実装するすべてのオブジェクトを含む、java.lang.AutoCloseableを実装するすべてのオブジェクトをリソースとして使用できます。

次のコードを想定します。

try( Connection con = null;
     Statement stmt = con.createStatement();
     Result rs= stmt.executeQuery(QUERY);)
{  
     count = rs.getInt(1);
}

例外が発生した場合、3つのリソースそれぞれに対して、作成された順序とは逆の順序でcloseメソッドが呼び出されます。つまり、最初にResultSetmに対してcloseメソッドが呼び出され、次にステートメントが呼び出され、最後にConnectionオブジェクトに対して呼び出されます。

また、closeメソッドが自動的に呼び出されたときに発生する例外が抑制されることを理解することも重要です。これらの抑制された例外は、Throwableクラスで定義されたgetsuppressed()メソッドによって取得できます。

ソース:https : //docs.oracle.com/javase/tutorial/essential/exceptions/tryResourceClose.html


この回答では、このアプローチとOPの投稿されたサンプルコードの動作の違いについては触れられていないようです。
Nathan Hughes

2
OPブロックとは異なり、tryブロックの部分が正常に完了してもcloseメソッドが完了しない場合、try-with-resourcesを使用すると、クローズ時に例外がスローされます。動作の変化を認めずに代替として推奨することは、誤解を招く可能性があります。
Nathan Hughes

例外をスローせず、closeメソッドが自動的に呼び出されて抑制されます。
Soroosh 2015

2
私が説明したケースを試してください。tryブロックは正常に完了し、closeは何かをスローします。リンクを投稿したページを再度読み取ると、抑制はtryブロックが何かをスローしたときにのみ適用されます。
Nathan Hughes、

3

「最終的に」ブロックで発生する例外を無視することは、それらの例外が何であり、それらが表す条件を知らない限り、一般に悪い考えです。通常のtry/finally使用パターンでは、tryブロックは外部コードが予期しない状態に物事を置き、ブロックは外部コードが予期finallyするものにそれらの物事の状態を復元します。例外をキャッチする外部コードでは、例外にもかかわらず、すべてがnormal状態。たとえば、あるコードがトランザクションを開始してから、2つのレコードを追加しようとしたとします。「最終的に」ブロックは「コミットされていない場合はロールバック」操作を実行します。呼び出し元は、2番目の「追加」操作の実行中に例外が発生する準備ができている可能性があり、そのような例外をキャッチした場合、データベースはいずれかの操作が試行される前の状態になると期待できます。ただし、ロールバック中に2番目の例外が発生した場合、呼び出し元がデータベースの状態について何らかの仮定を行うと、問題が発生する可能性があります。ロールバックの失敗は、重大な危機を表しています。単なる「レコードの追加に失敗しました」という例外を予期しているコードに捕まってはなりません。

私の個人的な傾向は、finallyメソッドで発生した例外をキャッチして「CleanupFailedException」にラップすることです。このような失敗は大きな問題を表し、そのような例外は軽くキャッチされるべきではないことを認識しています。


2

2つの例外が2つの異なるクラスである場合の1つの解決策

try {
    ...
    }
catch(package1.Exception err)
   {
    ...
   }
catch(package2.Exception err)
   {
   ...
   }
finally
  {
  }

しかし、この2回目のtry-catchを回避できない場合があります。たとえば、ストリームを閉じるため

InputStream in=null;
try
 {
 in= new FileInputStream("File.txt");
 (..)// do something that might throw an exception during the analysis of the file, e.g. a SQL error
 }
catch(SQLException err)
 {
 //handle exception
 }
finally
 {
 //at the end, we close the file
 if(in!=null) try { in.close();} catch(IOException err) { /* ignore */ }
 }

「using」ステートメントを使用した場合、リソースをクリーンアップする必要があります。
チャックコンウェイ

私の悪い、それはC#であると想定しています。
チャックコンウェイ

1

追加のブロックを避けたいのはなぜですか?finallyブロックには、例外をスローする可能性のある「通常の」操作が含まれているため、finallyブロックを完全に実行するには、例外をキャッチする必要があります。

finallyブロックが例外をスローすることを期待せず、とにかく例外を処理する方法がわからない場合(スタックトレースをダンプするだけです)、例外を呼び出しスタックにバブルアップさせます(finallyからtry-catchを削除します)ブロック)。

タイプを減らしたい場合は、「グローバルな」外側のtry-catchブロックを実装できます。これにより、finallyブロックでスローされたすべての例外がキャッチされます。

try {
    try {
        ...
    } catch (Exception ex) {
        ...
    } finally {
        ...
    }

    try {
        ...
    } catch (Exception ex) {
        ...
    } finally {
        ...
    }

    try {
        ...
    } catch (Exception ex) {
        ...
    } finally {
        ...
    }
} catch (Exception ex) {
    ...
}

2
-1これも。単一の最終ブロックで複数のリソースを閉じようとするとどうなりますか?最初のリソースのクローズに失敗した場合、例外がスローされると、他のリソースは開いたままになります。
アウトロープログラマ、

これが、最終的にブロックが完了したことを確認したい場合は、例外をキャッチする必要があることをポールに伝えた理由です。全体の答えを読んでください!
Eduard Wirch、

1

多くの検討の結果、次のコードが最適であることがわかりました。

MyResource resource = null;
try {
    resource = new MyResource();
    resource.doSomethingFancy();
    resource.close(); 
    resource = null;  
} finally {
    closeQuietly(resource)
}

void closeQuietly(MyResource a) {
    if (a!=null)
        try {
             a.close();
        } catch (Exception e) {
             //ignore
        }
}

そのコードは以下を保証します:

  1. コードが終了すると、リソースは解放されます
  2. リソースを閉じるときにスローされる例外は、それらを処理しないと消費されません。
  3. コードはリソースを2回閉じようとせず、不要な例外が作成されることはありません。

resource.close();の呼び出しを回避することもできます。tryブロックのresource = nullは、最終的にブロックが対象となるものです。また、「ファンシーなことをしている」ときにスローされた例外は処理しないことに注意してください。実際には、スタックの上位のアプリケーションレベルでインフラストラクチャの例外を処理する方がよいと思います。
ポール

resource.close()がスローされ、例外も発生する可能性があります。つまり、バッファのフラッシュが失敗した場合です。この例外は決して消費されるべきではありません。ただし、以前に発生した例外の結果としてストリームを閉じる場合は、例外を無視して根本原因を保持しながら、リソースを静かに閉じる必要があります。
Grogi 2013

0

可能であれば、最初にエラー状態を回避するためにテストする必要があります。

try{...}
catch(NullArgumentException nae){...}
finally
{
  //or if resource had some useful function that tells you its open use that
  if (resource != null) 
  {
      resource.Close();
      resource = null;//just to be explicit about it was closed
  }
}

また、回復できる例外のみをキャッチする必要があります。回復できない場合は、プログラムのトップレベルに伝播させます。エラー条件をテストできない場合は、既に行ったようにコードをtry catchブロックで囲む必要があります(ただし、特定の予期されるエラーをキャッチすることをお勧めします)。


エラー状態のテストは、例外が高価であるため、一般的には良い習慣です。
Dirk Vollmar、2009年

「防御的プログラミング」は時代遅れのパラダイムです。すべてのエラー状態のテストから生じる肥大化したコードは、最終的に、解決するよりも多くの問題を引き起こします。TDDと例外処理は、その現代的なアプローチIMHO
Joe Soul-bringer

@ジョー-私はすべてのエラー状態のテストに同意しませんが、特に例外と例外自体を回避するための単純なチェックのコストの(通常は)違いに照らして、時にはそれが理にかなっています。
ケンヘンダーソン

1
-1ここで、resource.Close()は例外をスローできます。追加のリソースを閉じる必要がある場合、例外によって関数が戻り、リソースは開いたままになります。それがOPの2番目のtry / catchの目的です。
アウトロープログラマ

@Outlaw-Closeが例外をスローし、リソースが開いている場合、私のポイントを見逃していて、例外をキャプチャして抑制することで、問題を修正するにはどうすればよいですか?それで私はなぜそれを伝播させたのですか(それを開いたままで回復できるのはかなりまれです)。
ケンヘンダーソン

0

これを別の方法にリファクタリングできます...

public void RealDoSuff()
{
   try
   { DoStuff(); }
   catch
   { // resource.close failed or something really weird is going on 
     // like an OutOfMemoryException 
   }
}

private void DoStuff() 
{
  try 
  {}
  catch
  {
  }
  finally 
  {
    if (resource != null) 
    {
      resource.close(); 
    }
  }
}

0

私は通常これを行います:

MyResource r = null;
try { 
   // use resource
} finally {   
    if( r != null ) try { 
        r.close(); 
    } catch( ThatSpecificExceptionOnClose teoc ){}
}

理論的根拠:リソースを使い終わって、唯一の問題がリソースを閉じることである場合、それについてできることはあまりありません。とにかくリソースを使い終わった場合、スレッド全体を強制終了することも意味がありません。

これは、少なくとも私にとっては、そのチェックされた例外を無視しても安全な場合の1つです。

今日まで、私はこのイディオムを使用して問題を抱えていませんでした。


将来リークが見つかった場合に備えて、ログに記録します。あなたはどこから来て(ない)可能性がある場所を知るだろうその方法
Egwor

@Egwor。仰るとおりです。これはほんのちょっとしたスミペットでした。私もそれをログに記録し、おそらくキャッチを使用すると、例外を除いて何かを行うことができます:)
OscarRyz

0
try {
    final Resource resource = acquire();
    try {
        use(resource);
    } finally {
        resource.release();
    }
} catch (ResourceException exx) {
    ... sensible code ...
}

仕事が終わりました。nullテストはありません。単一のキャッチ。取得および解放の例外を含みます。もちろん、Execute Aroundイディオムを使用でき、リソースタイプごとに1回だけ記述する必要があります。


5
use(resource)が例外Aをスローし、resource.release()が例外Bをスローするとどうなりますか?例外Aが失われる...
Darron、

0

変更Resourceから最良の答えCloseable

ストリームの実装Closeableしたがって、すべてのストリームに対してメソッドを再利用できます

protected void closeQuietly(Closeable resource) {
    if (resource == null) 
        return;
    try {
        resource.close();
    } catch (IOException e) {
        //log the exception
    }
}

0

リソースでtryを使用できない同様の状況に遭遇しましたが、closeQuietlyメカニズムのようにログに記録して無視するだけでなく、closeからの例外も処理したいと思いました。私の場合、実際には出力ストリームを処理していないため、単純なストリームよりもクローズ時の失敗の方が重要です。

IOException ioException = null;
try {
  outputStream.write("Something");
  outputStream.flush();
} catch (IOException e) {
  throw new ExportException("Unable to write to response stream", e);
}
finally {
  try {
    outputStream.close();
  } catch (IOException e) {
    ioException = e;
  }
}
if (ioException != null) {
  throw new ExportException("Unable to close outputstream", ioException);
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.