Javaのfinallyブロックから戻る


176

最近、Javaのfinallyブロックにreturnステートメントを含めることが可能であることを知って驚いた。

finally節に戻らないでください」で説明されているように、多くの人がそれを行うのは悪いことだと思っているようです。少し深くスクラッチすると、「Javaのリターンは常にではない」こともわかりました。これは、finallyブロック内の他のタイプのフロー制御のかなり恐ろしい例を示しています。

だから、私の質問は、finallyブロックのreturnステートメント(または他のフロー制御)がより良い/より読みやすいコードを生成する例を誰かに教えてもらえますか?

回答:


89

あなたが提供した例は、最終的にフロー制御を使用しない十分な理由です。

「より良い」という不自然な例がある場合でも、後でコードを保守する必要があり、微妙な点を認識していない開発者を検討してください。その貧しい開発者はあなたかもしれません...


5
承知しました。誰かが本当に良い説得力のある例を私に与えることができるように私は尋ねていると思います。
Matt Sheppard、

daosの@MattSheppardでは、クエリの終了をtry-finally
Blake

147

これによって引き起こされたバグを数年前に追跡するのは本当に大変でした。コードは次のようなものでした:

Object problemMethod() {
    Object rtn = null;
    try {
        rtn = somethingThatThrewAnException();
    }
    finally {
        doSomeCleanup();
        return rtn;
    }
}

起こったのは、例外が他のコードでスローされたということです。キャッチされ、ログに記録され、somethingThatThrewAnException()メソッド内で再スローされました。しかし、例外は過去に伝播されていませんでしたproblemMethod()。これを長い間見続けた後、最終的にreturnメソッドまで追跡しました。finallyブロックのreturnメソッドは、tryブロックで発生した例外がキャッチされなかったとしても、それが伝播しないように基本的に停止していました。

他の人が言ったように、Java仕様によると、finallyブロックから戻ることは合法ですが、これは悪いことであり、実行すべきではありません。


では、どこにリターンを置くべきでしょうか?
パーサー

@parsecer tryブロック内でsomethingThatThrewAnException()を呼び出した直後に言う
Tiago Sippert

@parsecer、?? 最後に、通常の方法でそれを実行してください。
パセリエ

21

-Xlint:finallyを使用すると、javacは最終的に戻りを警告します。もともとjavacは警告を発しませんでした-コードに問題がある場合、コンパイルに失敗するはずです。残念ながら、後方互換性は、予期しない独創的な愚かさを禁止できないことを意味します。

例外はfinallyブロックからスローできますが、その場合、示されている動作はほぼ確実にあなたが望むものです。


13

制御構造を追加し、finally {}ブロックに戻ることは、「できるから」という不正行為のもう1つの例であり、事実上すべての開発言語に散らばっています。ジェイソンは、それが簡単にメンテナンスの悪夢になる可能性があることを示唆して正しかった-関数からの早期のリターンに対する議論は、この「レイトリターン」の場合にも当てはまる。

最後に、ブロックは1つの目的のために存在し、先行するすべてのコードで何が起こったとしても、自分自身を完全に片付けられるようにします。これは主に、ファイルポインターやデータベース接続などを閉じたり解放したりすることですが、別注の監査を追加すると言って引き伸ばされているのを見ることができました。

関数の戻りに影響するものはすべて、try {}ブロックに置く必要があります。外部状態をチェックし、時間のかかる操作を行い、無効になった場合にその状態を再度チェックするメソッドがあったとしても、2番目のチェックがtry {}の内部に残っているかどうかを確認します。長い操作が失敗した場合、その状態を不必要にもう一度チェックすることになります。


6

簡単なGroovyテスト:

public class Instance {

  List<String> runningThreads = new ArrayList<String>()

  void test(boolean returnInFinally) {

    println "\ntest(returnInFinally: $returnInFinally)"
    println "--------------------------------------------------------------------------"
    println "before execute"
    String result = execute(returnInFinally, false)
    println "after execute -> result: " + result
    println "--------------------------------------------------------------------------"

    println "before execute"
    try {
      result = execute(returnInFinally, true)
      println "after execute -> result: " + result
    } catch (Exception ex) {
      println "execute threw exception: " + ex.getMessage()
    }  
    println "--------------------------------------------------------------------------\n"

  }

  String execute(boolean returnInFinally, boolean throwError) {
      String thread = Thread.currentThread().getName()
      println "...execute(returnInFinally: $returnInFinally, throwError: $throwError) - thread: $thread"
      runningThreads.add(thread)
      try {
        if (throwError) {
          println "...error in execute, throw exception"
          throw new Exception("as you liked :-)")
        }
        println "...return 'OK' from execute"
        return "OK"
      } finally {
        println "...pass finally block"
        if (returnInFinally) return "return value from FINALLY ^^"
        // runningThreads.remove(thread)
      }
  }
}

Instance instance = new Instance()
instance.test(false)
instance.test(true)

出力:

test(returnInFinally: false)
-----------------------------------------------------------------------------
before execute
...execute(returnInFinally: false, throwError: false) - thread: Thread-116
...return 'OK' from execute
...pass finally block
after execute -> result: OK
-----------------------------------------------------------------------------
before execute
...execute(returnInFinally: false, throwError: true) - thread: Thread-116
...error in execute, throw exception
...pass finally block
execute threw exception: as you liked :-)
-----------------------------------------------------------------------------


test(returnInFinally: true)
-----------------------------------------------------------------------------
before execute
...execute(returnInFinally: true, throwError: false) - thread: Thread-116
...return 'OK' from execute
...pass finally block
after execute -> result: return value from FINALLY ^^
-----------------------------------------------------------------------------
before execute
...execute(returnInFinally: true, throwError: true) - thread: Thread-116
...error in execute, throw exception
...pass finally block
after execute -> result: return value from FINALLY ^^
-----------------------------------------------------------------------------

質問:

私にとって興味深い点の1つは、Groovyが暗黙のリターンを処理する方法を確認することでした。Groovyでは、メソッドから値を「返す」ことができます(戻り値なしで)値を最後に残すだけです。finallyステートメントのrunningThreads.remove(..)行のコメントを外すとどうなると思いますか?これは通常の戻り値( "OK")を上書きして例外をカバーしますか?!


0

finallyブロック内から戻るexceptionsと失われます。

finallyブロック内のreturnステートメントは、tryまたはcatchブロックでスローされる可能性のある例外を破棄します。

Java言語仕様によると

その他の理由Rでtryブロックの実行が突然完了した場合、finallyブロックが実行され、次に選択肢があります。

   If the finally block completes normally, then the try statement
   completes  abruptly for reason R.

   If the finally block completes abruptly for reason S, then the try
   statement  completes abruptly for reason S (and reason R is
   discarded).

注:JLS 14.17に従い、returnステートメントは常に突然終了します。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.