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

开发 后端
本章节我们从字节码的角度来探究下return和finally到底哪个先执行。下面先来看一段简单地源码。

[[348914]]

本章节我们从字节码的角度来探究下return和finally到底哪个先执行。下面先来看一段简单地源码:

 

  1. public class ReturnFinallyDemo { 
  2.     public static void main(String[] args) { 
  3.         System.out.println(case1()); 
  4.     } 
  5.  
  6.     public static int case1() { 
  7.         int x; 
  8.         try { 
  9.             x = 1; 
  10.             return x; 
  11.         } finally { 
  12.             x = 3; 
  13.         } 
  14.     } 
  15.  
  16. # 输出 

上述代码的输出可以简单地得出结论:return在finally之前执行,我们来看下字节码层面上发生了什么事情。下面截取case1方法的部分字节码,并且对照源码,将每个指令的含义注释在后面:

 

  1. iconst_1 // 将常量1推入操作数栈顶  
  2. istore_0 // 弹出栈顶元素(1),保存到局部变量表slot[0],此时slot[0]=1。这两条指令对应源码:x = 1;  
  3. iload_0 // 将局部变量表slot[0]的值推入操作数栈顶,也就是说把上面x的值推入栈顶  
  4. istore_1 // 弹出栈顶元素(1),保存到局部变量表slot[1],此时slot[1]=1。其实,此时就已经把要return的值准备好了  
  5. iconst_3 // 将常量3推入操作数栈顶,这一条指令开始,其实是开始执行finally中的代码了  
  6. istore_0 // 弹出栈顶元素(3),保存到局部变量表slot[0],此时slot[0]=3。这两个指令对应源码:x = 3;这里要注意的是,虽然都是更新了x的值,但是finally中的x和try中x的赋值,保存在了不同的局部变量表中 
  7. iload_1 // 将局部变量表slot[1]的值推入操作数栈顶,此时栈顶元素的值为1,是第3行指令保存的值 
  8.  
  9. ireturn // 将操作数栈顶的值返回给调用方 

从字节码来看,似乎又是finally的代码先执行了,因为ireturn指令确实是在最后执行的,所以返回什么样的值不在于谁先执行,而在于ireturn指令返回的操作数栈顶的元素是何时保存的。在上述代码环境中,是try代码块中給x赋值的版本,也就是紧接着return语句后面的x所保存的版本。

下面再来看一个稍微复杂点的场景:

 

  1. public static int case2() { 
  2.     int x; 
  3.     try { 
  4.         x = 1; 
  5.         return ++x; 
  6.     } finally { 
  7.         x = 3; 
  8.     } 
  9.  
  10. # 输出 

有了上面的分析,这个就很好理解了,我们还是来看下字节码:

 

  1. iconst_1 // 将常量1推入操作数栈顶 
  2. istore_0 // 弹出栈顶元素(1),保存到局部变量表slot[0],此时slot[0]=1。这两条指令对应源码:x = 1; 
  3. iinc          0, 1 // 对局部变量表slot[0]进行自增(+1)操作,此时slot[0]=2,对应源码:++x;所以,可以看出return后面的表达式先执行 
  4. iload_0 // 将局部变量表slot[0]的值推入操作数栈顶,也就是说把上面x的值(2)推入栈顶 
  5. istore_1 // 弹出栈顶元素(2),保存到局部变量表slot[1],此时slot[1]=2。其实,此时就已经把要return的值准备好了 
  6. iconst_3 // 将常量3推入操作数栈顶,这一条指令开始,其实是开始执行finally中的代码了 
  7. istore_0 // 弹出栈顶元素(3),保存到局部变量表slot[0],此时slot[0]=3。这两个指令对应源码:x = 3;这里要注意的是,虽然都是更新了x的值,但是finally中的x和try中x的赋值,保存在了不同的局部变量表中 
  8. iload_1 // 将局部变量表slot[1]的值推入操作数栈顶,此时栈顶元素的值为2,是第6行指令保存的值,也就是经过++x之后的值 
  9. ireturn // 将操作数栈顶的值返回给调用方 

从上述代码可以看出,return后面的指令先执行,然后保存到局部变量表,接着执行finally中的语句,最后执行return指令本身。

总结一下,return指令是最后执行的,如果return后面有表达式,则执行完表达式之后就执行finally中的语句,最后再执行return指令。所以说finally和return到底哪个先执行:return指令后面如果有表达式或方法调用的话,先执行,然后执行finally,最后执行return指令。就像上面的程序演示的结果,不能光从x的赋值来看最终返回结果,从指令层面看,两次对x的赋值,保存在局部变量表的位置不一样。

最后,再来看一个平时不会这么去写的场景:

 

  1. public static int case3() { 
  2.     int x; 
  3.     try { 
  4.         x = 1; 
  5.         return ++x; 
  6.     } finally { 
  7.         x = 3; 
  8.         return x; 
  9.     } 
  10. # 输出 

这是一个finally返回结果的示例,平时不建议这么写,我们同样从字节码的角度来分析下:

 

  1. iconst_1 // 将常量1推入操作数栈顶 
  2. istore_0 // 弹出栈顶元素(1),保存到局部变量表slot[0],此时slot[0]=1。这两条指令对应源码:x = 1; 
  3. iinc          0, 1 // 对局部变量表slot[0]进行自增(+1)操作,此时slot[0]=2,对应源码:++x;所以,可以看出return后面的表达式先执行 
  4. iload_0  // 将局部变量表slot[0]的值推入操作数栈顶,也就是说把上面x的值(2)推入栈顶 
  5. istore_1 // 弹出栈顶元素(2),保存到局部变量表slot[1],此时slot[1]=2。 
  6. iconst_3 // 将常量3推入操作数栈顶,这一条指令开始,其实是开始执行finally中的代码了 
  7. istore_0 // 弹出栈顶元素(3),保存到变量表slot[0],此时slot[0]=3。这两个指令对应源码:x = 3 
  8. iload_0  // 将局部变量表slot[0]的值(3)推入操作数栈,这是跟之前不一样的地方,ireturn返回的值选择的局部变量表不一样 
  9. ireturn 

从字节码以及解释来看,直接忽略了try语句块中的return指令,这样的代码会让人产生疑惑,所以平时不建议这么写。本章节就到这里了。

责任编辑:未丽燕 来源: 今日头条
相关推荐

2017-03-02 14:52:46

2023-12-18 13:10:00

finally死锁JVM 崩溃

2020-06-28 09:08:08

Java语法块开发

2019-07-27 09:40:56

MySQLPG数据库

2023-05-28 13:03:46

BeegoGin设计

2022-10-11 10:18:12

数据硬盘开机

2024-07-10 10:54:44

2021-03-27 10:56:17

promisethenfinally

2021-08-06 22:47:37

编程语言数据工具

2024-11-19 08:10:00

2021-03-19 07:40:22

缓存数据库日志

2020-06-08 11:30:04

PGMySQL数据库

2021-09-07 13:15:01

语言return 1return 0

2012-06-02 00:53:39

Javafinally

2021-01-13 05:23:27

缓存数据库高并发

2021-07-26 08:12:31

开源API网关

2022-08-27 14:42:45

Java集合数组

2019-03-07 09:56:09

运营商无线网络信号

2011-11-18 09:26:18

Javafinally
点赞
收藏

51CTO技术栈公众号