ソース コード:
public class ReturnFinallyDemo { public static void main(String[] args) { System.out.println(case1()); } public static int case1() { int x; try { x = 1; return x; } finally { x = 3; } } } # 输出
上記のコードの出力は、次のように単純に結論付けることができます:finally の前に return が実行されます。バイトコード レベルで何が起こるかを見てみましょう。以下は、case1 メソッドのバイトコードの一部をインターセプトし、ソース コードと比較して、各命令の意味に注釈を付けます:
iconst_1 // 将常量1推入操作数栈顶 istore_0 // 弹出栈顶元素(1),保存到局部变量表slot[0],此时slot[0]=1。这两条指令对应源码:x = 1; iload_0 // 将局部变量表slot[0]的值推入操作数栈顶,也就是说把上面x的值推入栈顶 istore_1 // 弹出栈顶元素(1),保存到局部变量表slot[1],此时slot[1]=1。其实,此时就已经把要return的值准备好了 iconst_3 // 将常量3推入操作数栈顶,这一条指令开始,其实是开始执行finally中的代码了 istore_0 // 弹出栈顶元素(3),保存到局部变量表slot[0],此时slot[0]=3。这两个指令对应源码:x = 3;这里要注意的是,虽然都是更新了x的值,但是finally中的x和try中x的赋值,保存在了不同的局部变量表中 iload_1 // 将局部变量表slot[1]的值推入操作数栈顶,此时栈顶元素的值为1,是第3行指令保存的值 ireturn // 将操作数栈顶的值返回给调用方
バイトコードからは、ireturn のため、finally コードが最初に実行されるように見えます。この命令は実際に最後に実行されるため、返される値は誰が最初に実行するかには依存せず、ireturn 命令によって返されるオペランド スタックの最上位要素がいつ保存されるかによって決まります。上記のコード環境では、try コード ブロックで x に割り当てられたバージョン、つまり return ステートメントの直後に保存された x のバージョンです。
もう少し複雑なシナリオを見てみましょう:
public static int case2() { int x; try { x = 1; return ++x; } finally { x = 3; } } # 输出
上記の分析により、これは簡単に理解できます。バイトコードを見てみましょう:
iconst_1 // 将常量1推入操作数栈顶 istore_0 // 弹出栈顶元素(1),保存到局部变量表slot[0],此时slot[0]=1。这两条指令对应源码:x = 1; iinc 0, 1 // 对局部变量表slot[0]进行自增(+1)操作,此时slot[0]=2,对应源码:++x;所以,可以看出return后面的表达式先执行 iload_0 // 将局部变量表slot[0]的值推入操作数栈顶,也就是说把上面x的值(2)推入栈顶 istore_1 // 弹出栈顶元素(2),保存到局部变量表slot[1],此时slot[1]=2。其实,此时就已经把要return的值准备好了 iconst_3 // 将常量3推入操作数栈顶,这一条指令开始,其实是开始执行finally中的代码了 istore_0 // 弹出栈顶元素(3),保存到局部变量表slot[0],此时slot[0]=3。这两个指令对应源码:x = 3;这里要注意的是,虽然都是更新了x的值,但是finally中的x和try中x的赋值,保存在了不同的局部变量表中 iload_1 // 将局部变量表slot[1]的值推入操作数栈顶,此时栈顶元素的值为2,是第6行指令保存的值,也就是经过++x之后的值 ireturn // 将操作数栈顶的值返回给调用方
As can上記のコードから分かるように、return 後の命令が最初に実行され、次にローカル変数テーブルに保存され、最後に のステートメントが実行され、最後に return 命令自体が実行されます。
まとめると、return 命令は最後に実行され、return の後に式がある場合、式の実行後にfinallyの文が実行され、return 命令が最後に実行されます。したがって、最初に実行されるか、最後に実行されるか、リターン命令が実行されるか: return 命令の後に式またはメソッド呼び出しがある場合、最初に実行され、次に最後に実行され、最後に return 命令が実行されます。上記のプログラムのデモの結果と同様に、x の代入だけでは最終的な戻り結果を判断することはできず、命令レベルで見ると、x への 2 つの代入はローカル変数テーブルの異なる場所に格納されます。
最後に、通常はこのようには書かれないシナリオを見てみましょう:
public static int case3() { int x; try { x = 1; return ++x; } finally { x = 3; return x; } } # 输出
これは、最終的に結果を返す例です。通常、このように書くことはお勧めできません。バイトコードの観点から分析しましょう:
iconst_1 // 将常量1推入操作数栈顶 istore_0 // 弹出栈顶元素(1),保存到局部变量表slot[0],此时slot[0]=1。这两条指令对应源码:x = 1; iinc 0, 1 // 对局部变量表slot[0]进行自增(+1)操作,此时slot[0]=2,对应源码:++x;所以,可以看出return后面的表达式先执行 iload_0 // 将局部变量表slot[0]的值推入操作数栈顶,也就是说把上面x的值(2)推入栈顶 istore_1 // 弹出栈顶元素(2),保存到局部变量表slot[1],此时slot[1]=2。 iconst_3 // 将常量3推入操作数栈顶,这一条指令开始,其实是开始执行finally中的代码了 istore_0 // 弹出栈顶元素(3),保存到变量表slot[0],此时slot[0]=3。这两个指令对应源码:x = 3 iload_0 // 将局部变量表slot[0]的值(3)推入操作数栈,这是跟之前不一样的地方,ireturn返回的值选择的局部变量表不一样 ireturn
以上がJavaのreturn文とfinally文の実行順序は何ですか?の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。