当前位置: 首页 > 科技观察

Java中return和finally哪个先执行

时间:2023-03-16 14:46:03 科技观察

本章我们将从字节码的角度探讨return和finally哪个先执行。让我们看一个简单的源代码:=3;}}}#Output上面代码的输出可以简单得出一个结论:return在finally之前被执行,我们来看看字节码层面发生了什么。下面截取case1方法的部分字节码,对照源码,在后面注释每条指令的含义:iconst_1//将常量1压入操作数栈顶istore_0//弹出栈顶元素(1)并保存在本地变量表slot[0],此时slot[0]=1。这两条指令对应源码:x=1;iload_0//将局部变量表slot[0]的值压入操作数栈顶,也就是将上面x的值压入栈顶ofthestackistore_1//弹出栈顶元素(1),保存到局部变量表slot[1],此时slot[1]=1。其实这个时候要返回的值已经准备好了iconst_3//把常量3压入操作数栈顶。该指令开始执行finallyistore_0中的代码//弹出栈顶元素(3),保存到局部变量表slot[0],此时slot[0]=3。这两条指令对应源码:x=3;这里需要注意的是,虽然更新了x的值,但是finally中x和try中x的赋值存放在不同的局部变量表中iload_1//将局部变量表slot[1]的值推到最前面的操作数栈。此时栈顶元素的值为1,也就是第3行指令保存的值。ireturn//返回操作数栈顶的值给调用者从字节码看,看起来finally的代码是先执行的,因为ireturn指令确实是最后执行的,所以返回什么值并不取决于谁先执行,而是取决于ireturn指令执行时返回的操作数栈顶部的元素得救了。在上面的代码环境中,就是try代码块中分配给x的版本,也就是return语句之后x立即保存的版本。我们来看一个稍微复杂一点的场景:publicstaticintcase2(){intx;try{x=1;return++x;}finally{x=3;}}#Output有了上面的分析,这个就很容易理解了,我们来看atthebytecode:iconst_1//将常量1压入操作数栈顶istore_0//弹出栈顶元素(1),保存到局部变量表slot[0],此时slot[0]=1.这两条指令对应源码:x=1;iinc0,1//自增(+1)局部变量表slot[0],此时slot[0]=2,对应源码:++x;so,可以看出return后的表达式先执行了iload_0//将局部变量表slot[0]的值压入操作数栈顶,也就是将x的值压入上面(2)tothetopofthestoreistore_1//弹出栈顶元素(2)保存到局部变量表slot[1],此时slot[1]=2。其实这个时候要返回的值已经准备好了iconst_3//把常量3压入操作数栈顶。该指令开始执行finallyistore_0中的代码//弹出栈顶元素(3),保存到局部变量表slot[0],此时slot[0]=3。这两条指令对应源码:x=3;这里需要注意的是,虽然更新了x的值,但是finally中x和try中x的赋值存放在不同的局部变量表中iload_1//将局部变量表slot[1]的值推到最前面的操作数栈。此时栈顶元素的值为2,是第6行指令保存的值,即++xireturn//operate后的值栈顶的值为返回给调用者。从上面的代码可以看出,先执行return之后的指令,然后保存到局部变量表中,然后执行finally中的语句,最后执行return指令本身。综上所述,返回指令是最后执行的。如果return后面有表达式,则执行完表达式后执行finally中的语句,最后执行return指令。那么finally和return先执行哪个:如果return指令后面有表达式或方法调用,则先执行,然后finally,最后执行return指令。就像上面程序演示的结果一样,只从x的赋值是看不到最终返回结果的。从指令的角度来看,对x的两次赋值存储在局部变量表的不同位置。最后再看一个通常不会这样写的场景:publicstaticintcase3(){intx;try{x=1;return++x;}finally{x=3;returnx;}}#Output这是一个finallyreturnresult不建议这样写,我们也从字节码的角度来分析:iconst_1//将常量1压入栈顶istore_0//弹出栈顶元素(1),保存到局部变量表中slot[0],此时slot[0]=1。这两条指令对应源码:x=1;iinc0,1//自增(+1)局部变量表slot[0],此时slot[0]=2,对应源码:++X;so,可以看出return后的表达式先执行了iload_0//将局部变量表slot[0]的值压入操作数栈顶,也就是将x的值压入上面(2)tothetopofthestoreistore_1//弹出栈顶元素(2)保存到局部变量表slot[1],此时slot[1]=2。iconst_3//将常量3压入操作数栈顶。该指令实际上开始执行finally中的代码。[0]=3。这两条指令对应源码:x=3iload_0//将局部变量表slot[0]的值(3)压入操作数栈。这与上一个不同。ireturn返回的值选择的局部变量表不同。同样,ireturn从字节码和解释的角度直接忽略了try语句块中的return指令。这样的代码会让人看不懂,所以通常不建议这样写。这就是本章的全部内容。