Java中return和finally到底哪个先执行

本章节我们从字节码的角度来探究下return和finally到底哪个先执行 。 下面先来看一段简单地源码:
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;}}}# 输出1【Java中return和finally到底哪个先执行】上述代码的输出可以简单地得出结论:return在finally之前执行 , 我们来看下字节码层面上发生了什么事情 。 下面截取case1方法的部分字节码 , 并且对照源码 , 将每个指令的含义注释在后面:
0: iconst_1 // 将常量1推入操作数栈顶1: istore_0 // 弹出栈顶元素(1) , 保存到局部变量表slot[0] , 此时slot[0]=1 。 这两条指令对应源码:x = 1;2: iload_0// 将局部变量表slot[0]的值推入操作数栈顶 , 也就是说把上面x的值推入栈顶3: istore_1 // 弹出栈顶元素(1) , 保存到局部变量表slot[1] , 此时slot[1]=1 。 其实 , 此时就已经把要return的值准备好了4: iconst_3 // 将常量3推入操作数栈顶 , 这一条指令开始 , 其实是开始执行finally中的代码了5: istore_0 // 弹出栈顶元素(3) , 保存到局部变量表slot[0] , 此时slot[0]=3 。 这两个指令对应源码:x = 3;这里要注意的是 , 虽然都是更新了x的值 , 但是finally中的x和try中x的赋值 , 保存在了不同的局部变量表中6: iload_1 // 将局部变量表slot[1]的值推入操作数栈顶 , 此时栈顶元素的值为1 , 是第3行指令保存的值7: ireturn // 将操作数栈顶的值返回给调用方从字节码来看 , 似乎又是finally的代码先执行了 , 因为ireturn指令确实是在最后执行的 , 所以返回什么样的值不在于谁先执行 , 而在于ireturn指令返回的操作数栈顶的元素是何时保存的 。 在上述代码环境中 , 是try代码块中給x赋值的版本 , 也就是紧接着return语句后面的x所保存的版本 。
下面再来看一个稍微复杂点的场景:
public static int case2() {int x;try {x = 1;return ++x;} finally {x = 3;}}# 输出2有了上面的分析 , 这个就很好理解了 , 我们还是来看下字节码:
0: iconst_1 // 将常量1推入操作数栈顶1: istore_0 // 弹出栈顶元素(1) , 保存到局部变量表slot[0] , 此时slot[0]=1 。 这两条指令对应源码:x = 1;2: iinc0, 1 // 对局部变量表slot[0]进行自增(+1)操作 , 此时slot[0]=2 , 对应源码:++x;所以 , 可以看出return后面的表达式先执行5: iload_0 // 将局部变量表slot[0]的值推入操作数栈顶 , 也就是说把上面x的值(2)推入栈顶6: istore_1 // 弹出栈顶元素(2) , 保存到局部变量表slot[1] , 此时slot[1]=2 。 其实 , 此时就已经把要return的值准备好了7: iconst_3 // 将常量3推入操作数栈顶 , 这一条指令开始 , 其实是开始执行finally中的代码了8: istore_0 // 弹出栈顶元素(3) , 保存到局部变量表slot[0] , 此时slot[0]=3 。 这两个指令对应源码:x = 3;这里要注意的是 , 虽然都是更新了x的值 , 但是finally中的x和try中x的赋值 , 保存在了不同的局部变量表中9: iload_1 // 将局部变量表slot[1]的值推入操作数栈顶 , 此时栈顶元素的值为2 , 是第6行指令保存的值 , 也就是经过++x之后的值10: ireturn // 将操作数栈顶的值返回给调用方从上述代码可以看出 , return后面的指令先执行 , 然后保存到局部变量表 , 接着执行finally中的语句 , 最后执行return指令本身 。
总结一下 , return指令是最后执行的 , 如果return后面有表达式 , 则执行完表达式之后就执行finally中的语句 , 最后再执行return指令 。 所以说finally和return到底哪个先执行:return指令后面如果有表达式或方法调用的话 , 先执行 , 然后执行finally , 最后执行return指令 。 就像上面的程序演示的结果 , 不能光从x的赋值来看最终返回结果 , 从指令层面看 , 两次对x的赋值 , 保存在局部变量表的位置不一样 。
最后 , 再来看一个平时不会这么去写的场景:
public static int case3() {int x;try {x = 1;return ++x;} finally {x = 3;return x;}}# 输出3这是一个finally返回结果的示例 , 平时不建议这么写 , 我们同样从字节码的角度来分析下:
0: iconst_1 // 将常量1推入操作数栈顶1: istore_0 // 弹出栈顶元素(1) , 保存到局部变量表slot[0] , 此时slot[0]=1 。 这两条指令对应源码:x = 1;2: iinc0, 1 // 对局部变量表slot[0]进行自增(+1)操作 , 此时slot[0]=2 , 对应源码:++x;所以 , 可以看出return后面的表达式先执行5: iload_0// 将局部变量表slot[0]的值推入操作数栈顶 , 也就是说把上面x的值(2)推入栈顶6: istore_1 // 弹出栈顶元素(2) , 保存到局部变量表slot[1] , 此时slot[1]=2 。 7: iconst_3 // 将常量3推入操作数栈顶 , 这一条指令开始 , 其实是开始执行finally中的代码了8: istore_0 // 弹出栈顶元素(3) , 保存到变量表slot[0] , 此时slot[0]=3 。 这两个指令对应源码:x = 39: iload_0// 将局部变量表slot[0]的值(3)推入操作数栈 , 这是跟之前不一样的地方 , ireturn返回的值选择的局部变量表不一样10: ireturn