次のプログラムを実行すると、何の値が表示されるか分かりますか?
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が実行されるなら、いかにしてreturnとfinallyの両方の実行が実現するのでしょうか。
もし答えが分からなければ、どうぞこのまま読み進めてください。
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(中国語サイト)