今天去逛论坛 时发现了一个颇有趣的问题:html
谁能给我我解释一下这段程序的结果为何是:2.而不是:3java
代码以下:程序员
class Test { public int aaa() { int x = 1; try { return ++x; } catch (Exception e) { } finally { ++x; } return x; } public static void main(String[] args) { Test t = new Test(); int y = t.aaa(); System.out.println(y); } }
看了问题后,得出了如下几个问题:oracle
刚看到这个问题后。忽然发现基础不够扎实,竟然来第一个都答不出来。。。(不知道还有木有和我也同样也回答不出以上的问题的? 若是有请在评论里告诉我一声,让我知道,我并不孤独~~)app
根据已有的知识知道:
return 是能够看成终止语句来用的,咱们常常用它来跳出当前方法,并返回一个值给调用方法。而后该方法就结束了,不会执行return下面的语句。
finally :不管try语句发生了什么,不管抛出异常仍是正常执行。finally语句都会执行。
那么问题来了。。。。在try语句里使用return后,finally是否还会执行?finally必定会执行的说法是否还成立?若是成立,那么先执行return仍是先执行finally?jvm
在求知欲的驱动下,我继续进行更深的探索,果断打开了Oracle的主页,翻阅了java 官方教程的finally语句。发现了官方教程对这个特殊状况有说明:ide
The finally block always executes when the try block exits. This ensures that the finally block is executed even if an unexpected exception occurs. But finally is useful for more than just exception handling — it allows the programmer to avoid having cleanup code accidentally bypassed by a return, continue, or break. Putting cleanup code in a finally block is always a good practice, even when no exceptions are anticipated.函数
Note: If the JVM exits while the try or catch code is being executed, then the finally block may not execute. Likewise, if the thread executing the try or catch code is interrupted or killed, the finally block may not execute even though the application as a whole continues..net
我的简单翻译:线程
当try语句退出时确定会执行finally语句。这确保了即便发了一个意想不到的异常也会执行finally语句块。可是finally的用处不只是用来处理异常——它可让程序员不会由于return、continue、或者break语句而忽略了清理代码。把清理代码放在finally语句块里是一个很好的作法,即使可能不会有异常发生也要这样作。
注意,当try或者catch的代码在运行的时候,JVM退出了。那么finally语句块就不会执行。一样,若是线程在运行try或者catch的代码时被中断了或者被杀死了(killed),那么finally语句可能也不会执行了,即便整个运用还会继续执行。
从上面的官方说明,咱们知道不管try里执行了return语句、break语句、仍是continue语句,finally语句块还会继续执行。同时,在stackoverflow里也找到了一个答案,咱们能够调用System.exit()来终止它:
finally will be called.
The only time finally won't be called is: if you call System.exit(), another thread interrupts current one, or if the JVM crashes first.
另外,在java的语言规范有讲到,若是在try语句里有return语句,finally语句仍是会执行。它会在把控制权转移到该方法的调用者或者构造器前执行finally语句。也就是说,使用return语句把控制权转移给其余的方法前会执行finally语句。
咱们依然使用上面的代码做为例子。首先,分别在如下三行代码前加上断点:
而后以debug模式运行代码。
刚开始时,效果以下图:
按一下F6,咱们能够发现,程序已经执行到 return ++x;,但还没执行该语句,此刻x=1
继续按一下F6,程序执行到 ++x;,但还没执行该语句,所以此时的x=2(刚执行完return ++x语句的++x,但没执行return)
继续按一下F6,此时,咱们发现程序又跳回到 return +xx 这一行,此刻x=3(执行了finally语句里的++x)
从上面过程当中能够看到,
看到这,咱们可能会再次纠结起来了。从上面的验证能够看出,finally语句执行了,并且x的值也确实加到3了,那么为何y是2呢?
翻看官方的jvm规范就会把一切的谜团解开了:
If the try clause executes a return, the compiled code does the following:
- Saves the return value (if any) in a local variable.
- Executes a jsr to the code for the finally clause.
- Upon return from the finally clause, returns the value saved in the local variable.
简单翻译下:
若是try语句里有return,那么代码的行为以下:
1.若是有返回值,就把返回值保存到局部变量中
2.执行jsr指令跳到finally语句里执行
3.执行完finally语句后,返回以前保存在局部变量表里的值
根据上面的说明就能够轻易地解释为何是2了。
当执行到return ++x;时,jvm在执行完++x后会在局部变量表里另外分配一个空间来保存当前x的值。
注意,如今还没把值返回给y,而是继续执行finally语句里的语句。等执行完后再把以前保存的值(是2不是x)返回给y。
因此就有了y是2不是3的状况。
其实这里还有一点要注意的是,若是你在finally里也用了return语句,好比return +xx。那么y会是3。由于规范规定了,当try和finally里都有return时,会忽略try的return,而使用finally的return。
查看Test.class的字节码咱们一样也能够很轻松地知道为何是2而不是3:
大概讲讲指令操做顺序:
iconst_1: 把常数1进栈 ---> istore_1: 栈顶元素出栈并把元素保存在本地变量表的第二个位置里(下标为1的位置里) ---> iinc 1, 1 : 本地变量表的第二个元素自增1 --->iload_1:第二个元素进栈 ---> istore_2:栈顶元素出栈并把元素保存在本地变量表的第2个位置里 ---> iinc 1, 1 : 本地变量表的第二个元素自增1 ---> iload_2:第二个元素进栈 (注意,此时栈顶元素为2)---> ireturn:返回栈顶元素。
后面的指令是要在2-7行出现异常时在跳到12行的,这个例子没出现异常,不用关注。
上面流程栈和本地变量表的状况以下图:
参考资料:
Java虚拟机规范
java 官方教程的finally语句
IBM的Java字节码教程
深刻理解Java虚拟机(第2版)