try-catch-finallyのfinallyでreturnしない

finallyブロックにはreturn文を書かない

Javaで例外処理を実装する場合、「try-catch-finally」を利用します。

この際、finallyブロックにはreturn文を記述してはいけません。

「try-catch-finally」は以下の順番で処理が実行されます。

  1. tryブロックの中に記載した処理が実行される。
  2. 例外が発生した場合にcatchブロックで例外に対応した処理が実行される
  3. finallyブロックが必ず最後に実行される。

そのため、「finallyブロックでreturn文を記述」した場合、「tryブロックのreturn」と「catchブロックのreturn」は「処理が上書き」されて実行されません。

実際にコードを実行して動作を見たほうが分かりやすいのでやってみましょう。

finallyブロックでreturnしないパターン

public static void main(String[] args) {
    // 何がリターンされるか検証
    String actual = finallyNonReturn();
    System.out.println("4. mainメソッドで戻り値を受け取る");
    System.out.println(actual);
}

private static String finallyNonReturn() {
    try {
        System.out.println("1. 例外が発生する可能性のある処理");
        boolean isError = false; // 例外有無の制御用
        if (isError) {
            throw new Exception("処理中に例外発生");
        }

        // メソッドの戻り値
        return "methodのreturn";
    } catch (Exception e) {
        System.out.println("2. catch:例外をキャッチして例外処理を実行");
        // 例外をキャッチした後にエラーを表す戻り値を返す
        return "catchのreturn";
    } finally {
        // finallyで何らかの処理を実行
        System.out.println("3. finally:リソースのクローズなどを実行");
    }
}

finallyブロックでreturnをしていない場合は特に問題ありません。

コンソールへの出力はそれぞれ以下の通りとなります。

例外なし

1. 例外が発生する可能性のある処理
3. finally:リソースのクローズなどを実行
4. mainメソッドで戻り値を受け取る
methodのreturn

例外あり

1. 例外が発生する可能性のある処理
2. catch:例外をキャッチして例外処理を実行
3. finally:リソースのクローズなどを実行
4. mainメソッドで戻り値を受け取る
catchのreturn

特に違和感なく思った通りの動作ですね。

finallyブロックでreturnするパターン

public static void main(String[] args) {
    // 何がリターンされるか検証
    String actual = finallyReturn();
    System.out.println("4. mainメソッドで戻り値を受け取る");
    System.out.println(actual);
}

private static String finallyReturn() {
    try {
        System.out.println("1. 例外が発生する可能性のある処理");
        boolean isError = false; // 例外有無の制御用
        if (isError) {
            throw new Exception("処理中に例外発生");
        }

        // メソッドの戻り値
        return "methodのreturn";
    } catch (Exception e) {
        System.out.println("2. catch:例外をキャッチして例外処理を実行");
        // 例外をキャッチした後にエラーを表す戻り値を返す
        return "catchのreturn";
    } finally {
        System.out.println("3. finally:リソースのクローズなどを実行");
        // finallyで何らかの処理をしたのでその結果を返す(やってはいけない)
        boolean isFinallyReturn = true;
        if (isFinallyReturn) {
            return "finallyのreturn";
        }
    }
}

finallyブロックに記述したreturn文が実行されると「戻り値の上書き」が発生します。

実際に動かして、コンソールへの出力を見てみましょう。

例外なし、finallyでreturnあり

1. 例外が発生する可能性のある処理
3. finally:リソースのクローズなどを実行
4. mainメソッドで戻り値を受け取る
finallyのreturn

例外あり、finallyでreturnあり

1. 例外が発生する可能性のある処理
2. catch:例外をキャッチして例外処理を実行
3. finally:リソースのクローズなどを実行
4. mainメソッドで戻り値を受け取る
finallyのreturn

見ての通り「戻り値の上書き」が発生しました。

正常終了したら「methodのreturn」、例外が発生したら「catchのreturn」が返ってくるのが直感的な動きです。 しかし、finallyブロックにreturnを記述することで、直感的ではない戻り値の上書きが発生するようになります。

このような直感的ではない動作を作り込んでしまわないように、finallyブロックでは以下のことに気を付けて記述しましょう。

  1. finallyブロックにreturn文を書かない
  2. finallyブロックはリソースの開放のみを行う
  3. finallyブロックには処理を書かない(処理を書くとreturnを書きたくなっちゃう)
  4. 何か処理を書きたいのであれば、finallyブロックを抜けた後に書く
  5. JavaSE7以降であれば「try-with-resources」を積極的に利用する

そもそも、finallyブロックの目的は、使用しているリソースのclose処理を行うことです。 そのため、リソースのclose処理以外にfinallyブロックを利用しないようにしましょう。

利用しているリソースがAutoCloseableインタフェース、もしくは、Closeableインタフェースを実装しているものであれば「try-with-resources」を利用することで、リソースのclose処理をJavaVMにお任せすることができます。こうすることで、リソースのclose処理を「プログラマの責務」から「JavaVMの責務」に移すことができますので積極的に使っていきましょう!

「try-with-resources」を使っていればfinallyブロックを書く必要がありませんし、リソースのclose漏れも自動的に防げます!!