关于Java中try finally return语句的执行顺序浅析
问题分析
finally语句块一定会执行吗?
可能很多人第一反应是肯定要执行的,但仔细一想,如果一定会执行的话也就不会这么SB的问了。
Demo1
publicclassTest{ publicstaticvoidmain(String[]args){ System.out.println("returnvalueoftest():"+test()); } publicstaticinttest(){ inti=1; //if(i==1){ //return0; //} System.out.println("thepreviousstatementoftryblock"); i=i/0; try{ System.out.println("tryblock"); returni; }finally{ System.out.println("finallyblock"); } } }
Demo1的执行结果如下:
thepreviousstatementoftryblock Exceptioninthread"main"java.lang.ArithmeticException:/byzero atcom.becoda.bkms.bus.basics.web.Test2.test(Test2.java:15) atcom.becoda.bkms.bus.basics.web.Test2.main(Test2.java:5)
另外,如果去掉上例中的注释,执行结果则是:
returnvalueoftest():0
以上两种情况,finally语句块都没有执行,说明什么问题?只有与finally相对应的try语句块得到执行的情况下,finally语句块才会执行,而上面都是在try语句块之前返回(return)或者抛出异常,所以try对应的finally语句块没有执行。那么,即使与finally相对应的try语句块得到执行的情况下,finally语句块一定会执行吗?但下面例子
Demo2
publicclassTest{ publicstaticvoidmain(String[]args){ System.out.println("returnvalueoftest():"+test()); } publicstaticinttest(){ inti=1; try{ System.out.println("tryblock"); System.exit(0); returni; }finally{ System.out.println("finallyblock"); } } }
Demo2的执行结果如下:
tryblock
finally语句块还是没有执行,为什么呢?因为我们在try语句块中执行了System.exit(0)语句,终止了Java虚拟机的运行,虽然一般情况下我们不会这么干。还有情况是当一个线程在执行try语句块或者catch语句块时被打断(interrupted)或者被终止(killed),与其对应的finally语句块可能不会执行。还有更极端的情况,就是在线程运行try语句块或者catch语句块时,突然死机或者断电,finally语句块肯定不会执行了。
finally语句示例说明
下面看一个简单的例子
Demo3
publicclassTest{ publicstaticvoidmain(String[]args){ try{ System.out.println("tryblock"); return; }finally{ System.out.println("finallyblock"); } } }
Demo3的执行结果为:
tryblock finallyblock
Demo3说明finally语句块在try语句块中的return语句之前执行。我们再来看另一个例子。
Demo4
publicclassTest{ publicstaticvoidmain(String[]args){ System.out.println("returevalueoftest():"+test()); } publicstaticinttest(){ inti=1; try{ System.out.println("tryblock"); i=1/0; return1; }catch(Exceptione){ System.out.println("exceptionblock"); return2; }finally{ System.out.println("finallyblock"); } } }
Demo4的执行结果为:
tryblock exceptionblock finallyblock returevalueoftest():2
Demo4说明了finally语句块在catch语句块中的return语句之前执行。
从上面的Demo3和Demo4,我们可以看出,其实finally语句块时在try或者catch中的return语句之前执行的,更加一般的说法是,finally语句块应该是在控制转移语句之前执行,控制转移语句除了return外,还有break和continue。
再来看下面两个例子
Demo5
publicclassTest{ publicstaticvoidmain(String[]args){ System.out.println("returnvalueofgetValue():"+getValue()); } publicstaticintgetValue(){ try{ return0; }finally{ return1; } } }
Demo5的执行结果为:
returnvalueofgetValue():1
Demo6
publicclassTest{ publicstaticvoidmain(String[]args){ System.out.println("returnvalueofgetValue():"+getValue()); } publicstaticintgetValue(){ inti=1; try{ returni; }finally{ i++; } } }
Demo6的执行结果为:
returnvalueofgetValue():1
利用我们上面分析得出的结论:finally语句块是在try或者catch中的return语句之前执行的。由此,可以轻松的理解Demo5的执行结果是1。因为finally中的return1;语句要在try中的return0;语句之前执行,那么finally中的return1;语句执行后,把程序的控制权转交给了它的调用者main()函数,并且返回值为1。那为什么Demo6的返回值不是2,而是1呢?按照Demo5的分析逻辑,finally中的i++;语句应该在try中的returni;之前执行啊?i的初始值为1,那么执行i++;之后为2,再执行returni;那不就应该是2吗?怎么变成1了呢?
说明这个问题需要了解Java虚拟机是如何编译finally语句块的。
Java方法是在栈帧中执行,栈帧是线程私有栈的单位,执行方法的线程会为每一个方法分配一小块空间来作为该方法执行时的内存空间,栈帧分为三个区域:
1、操作数栈,用来保存正在执行的表达式中的操作数
2、局部变量区,用来保存方法中使用的变量,包括方法参数,方法内部声明的变量,以及方法中使用到的对象的成员变量或类的成员变量(静态变量),最后两种变量会复制到局部变量区,因此在多线程环境下,这种变量需要根据需要声明为volatile类型
3、字节码指令区
例如下面这段代码
try{ returnexpression; }finally{ dosomework; }
首先我们知道,finally语句是一定会执行,但他们的执行顺序是怎么样的呢?他们的执行顺序如下:
1、执行:expression,计算该表达式,结果保存在操作数栈顶;
2、执行:操作数栈顶值(expression的结果)复制到局部变量区作为返回值;
3、执行:finally语句块中的代码;
4、执行:将第2步复制到局部变量区的返回值又复制回操作数栈顶;
5、执行:return指令,返回操作数栈顶的值;
我们可以看到,在第一步执行完毕后,整个方法的返回值就已经确定了,由于还要执行finally代码块,因此程序会将返回值暂存在局部变量区,腾出操作数栈用来执行finally语句块中代码,等finally执行完毕,再将暂存的返回值又复制回操作数栈顶。所以无论finally语句块中执行了什么操作,都无法影响返回值,所以试图在finally语句块中修改返回值是徒劳的。因此,finally语句块设计出来的目的只是为了让方法执行一些重要的收尾工作,而不是用来计算返回值的。
这样就能解释Demo6的问题了
让我们再来看以下3个例子。
Demo7
publicclassTest{ publicstaticvoidmain(String[]args){ System.out.println("returnvalueofgetValue():"+getValue()); } @SuppressWarnings("finally") publicstaticintgetValue(){ inti=1; try{ i=4; }finally{ i++; returni; } } }
Demo7的执行结果为:
returnvalueofgetValue():5
Demo8
publicclassTest{ publicstaticvoidmain(String[]args){ System.out.println("returnvalueofgetValue():"+getValue()); } publicstaticintgetValue(){ inti=1; try{ i=4; }finally{ i++; } returni; } }
Demo8的执行结果为:
returnvalueofgetValue():5
Demo9
publicclassTest{ publicstaticvoidmain(String[]args){ System.out.println(test()); } publicstaticStringtest(){ try{ System.out.println("tryblock"); returntest1(); }finally{ System.out.println("finallyblock"); } } publicstaticStringtest1(){ System.out.println("returnstatement"); return"afterreturn"; } }
Demo9的执行结果为:
tryblock returnstatement finallyblock afterreturn
总结:
1、finally语句块不一定会被执行
2、finally语句块在try语句块中的return语句之前执行
3、finally语句块在catch语句块中的return语句之前执行
4、finally语句块中的return语句会覆盖try块中的return返回
5、试图在finally语句块中修改返回值不一定会被改变