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
- 空调|让格力、海尔都担忧,中国取暖“新潮物”强势来袭,空调将成闲置品?
- 国外|坐拥77件专利,打破国外的垄断,造出中国最先进的家电芯片
- 手机基带|为了5G降低4G网速?中国移动回应来了:罪魁祸首不是运营商
- 通气会|12月4~6日,2020中国信息通信大会将在成都举行
- 中国|浅谈5G移动通信技术的前世和今生
- 操盘|中兴统一操盘中兴、努比亚、红魔三大品牌
- Blade|售价2798元 中兴Blade 20 Pro 5G手机发布 骁龙765G配四摄
- 现状|程序员现状揭秘:平均年薪20.36万,Java人才需求量最大
- 健身房|乐刻韩伟:产业互联网中只做单环节很难让数据发挥大作用
- 垫底|5G用户突破2亿:联通垫底,电信月增700万,中国移动有多少?