POSTD PRODUCED BY NIJIBOX

POSTD PRODUCED BY NIJIBOX

ニジボックスが運営する
エンジニアに向けた
キュレーションメディア

Ke Pi

本記事は、原著者の許諾のもとに翻訳・掲載しております。

次のプログラムを実行すると、何の値が表示されるか分かりますか?

class Test {
    public int aaa() {
        int x = 1;

        try {
            return ++x;
        } catch (Exception e) {

        } finally {
            ++x;
        }
        return x;
    }

    public static void main(String[] args) {
        Test t = new Test();
        int y = t.aaa();
        System.out.println(y);
    }
}

上の質問に回答する前に、次の問題には答えられるでしょうか?

  • try ブロック内に return 文がある場合、 finally ブロックは return の実行時に処理されるのでしょうか。
  • finally が実行されるなら、いかにして returnfinally の両方の実行が実現するのでしょうか。

もし答えが分からなければ、どうぞこのまま読み進めてください。

Oracle Javaチュートリアル に、 try ブロック内に return 文があり、さらに finally ブロックも存在する特殊なケースの説明があります。

finallyブロックは、tryブロックが終了する時、常に実行します。予期しない例外が起こったとしても、finallyブロックは確実に実行されるということです。しかし、finallyは例外の対応以外にも便利に使えます。finallyを使うと、プログラマは「return・continue・breakによって、クリーンアップコードが意図せずバイパスされてしまう」といった事態を避けられるのです。たとえ例外の可能性がない時でも、finallyブロックにクリーンアップコードを置くのを習慣にすると良いでしょう。

注意: tryコード、またはcatchコードの実行中にJVMが終了した場合、finallyブロックは実行されないことがあります。同様に、try・catchコードを実行するスレッドが中断・停止した場合、アプリケーション全体が処理を続けていてもfinallyブロックは実行されない可能性があります。

上記は try ブロック内に return 文、 break 文、 continue 文のいずれがあっても、 finally が常に実行されると説明しています。一部の例外は、JVMの終了や try finally ブロックを実行するスレッドの中断です。これはつまり、 try ブロック内で System.exit(0) を呼び出すと、 finally ブロックは実行されないことを意味します。

それでは、上述のコードの出力は何になるのでしょうか。 答えは3ではなく2です。 なぜでしょう。 JVMの仕様 にその答えがあります。

もしtry節でreturnを実行した場合、コンパイルされたコードは以下のような処理をします。

  • ローカル変数に戻り値(もし存在する場合)をセーブする。
  • finally節のコードまでjsrを実行する。
  • finally節からのreturnによって、ローカル変数に格納された値を返す。

return ++x が実行されると、JVMは ++x の値を一時変数へ格納し、 finally ブロックを実行し続けます。 finally が実行されたあと、一時変数に格納されている値がメソッドの呼び出し元へ返されるはずです。よって出力は3ではなく2になります。

Test.classの javap の出力も以下のようになります。


コマンドの実行順序は以下のとおりです。

  • iconst_1:スタックに定数1をpush
  • istore_1:スタックの値をpopし、ローカル変数のテーブルのposition 1へ格納する
  • inc 1, 1: position 1で、ローカル変数を1でインクリメントする
  • iload_1: position 1のローカル変数をスタックへpush
  • istore_2: スタックから値をpopし、ローカル変数のテーブルのposition 2へ格納する
  • inc 1, 1: position 1のローカル変数を1でインクリメントする
  • iload_2: position 2のローカル変数をスタックへpush
  • ireturn: スタック(ここでは2)からpopされた値を戻す

1つ注意しておきたいことはもし finally ブロックにも return 文があった場合、メソッドの呼び出し元へ返されるのは finally ブロックの戻り値になるということです。というのは、仕様ではtryのreturnが無視され、tryとfinallyとの両方でreturn文がある場合finallyのreturnが戻ってきた値になるからです。

参照: http://www.cnblogs.com/averey/p/4379646.html (中国語サイト)