当问到finally代码块的执行顺序,就算刚刚学编程的小白都能毫不犹豫的说出答案:不管异常发生与否,finally语句块的代码一定会被执行!大体上这样讲是没有错,但是finally块中的代码一定会有效执行吗?答案是否定的。或许有人觉得这有什么关系吗,反正是执行了,讲这个有什么意义呢?我相信每个向上的人面对知识时都要有一颗近乎朝圣的心!其实明白与否很有意义,因为它涉及着你以后在finally代码块中的逻辑。话不多说,请看代码:
public class finallyTest { public static void main(String[] args) { System.out.println("main: x = "+test()); } private static int test() { int x = 1; try{ System.out.println("try: x = "+x); return x; }catch(Exception e){ throw new RuntimeException(e); }finally{ ++x; System.out.println("finally: x = "+x); } } }
运行结果如下:
从运行结果上分析,try语句中代码先执行这是毫无疑问了,此时 x=1, 然后根据我们以往所了解的一样,在return语句之前执行finally语句块中的代码,此时 x自增1 值变为2 并输出,从结果上来看也是没有错误的,但是,请注意,在try语句块中return给主函数的 x 的值应该是2啊,怎么会是1呢?finally语句中的代码明明是执行过的了啊,此时博主就猜想,难道JVM在执行return语句的时候又开了一个线程去执行finally代码块而自己只管返回结果?于是在这样的猜想下进行了下边的验证:
在执行return语句之前阻塞500毫秒,等待finally语句块执行完毕后再让return语句返回x的值,那么这次的结果会如何呢???
import java.util.*; import java.text.*; public class finallyTest { static int x = 1; public static void main(String[] args) { System.out.println("main: x = "+test() +" : "+printTime()); } private static int test() { try{ System.out.println("try: x = "+x +" : "+printTime()); Thread.sleep(500); return x; }catch(Exception e){ throw new RuntimeException(e); }finally{ ++x; System.out.println("finally: x = "+x +" :"+printTime()); } } //打印当前时间 格式为: 分:秒:毫秒 public static String printTime(){ Date date = new Date(System.currentTimeMillis()); SimpleDateFormat sdf = new SimpleDateFormat("mm:ss:SS"); return sdf.format(date); } }
执行结果如下:
读者读到这一步有没有觉有点耐人寻味呢?原来一个不起眼的finally语句块竟然还有这样的猫腻!明明 x 的值已经变成2了但是主函数接受到的仍然是1,这究竟是为什么呢?于是博主就四处搜寻了一些有关方面的介绍,于是有了如下猜想:我们都知道,return语句有两个作用,一是返回结果,二是终止执行,那么会不会是在执行到return语句时,先将x的值返回给调用者,然后再执行finally语句块中的代码,最后再执行终止的作用呢? 实践是检验真理的唯一标准!于是博主想到堆栈跟踪,注意了,接下来请仔细看下面的跟踪结果:
当然,程序运行到这儿并没有结束,因为以上还都是我的一些猜想,上午博主专门逃了一节课去图书馆翻阅了几本国外原版的比较权威的书也没有找到比较满意的答案,所以上边的只是我自己的一些猜想。所以自己心里也是虚得很。
在一筹莫展,不知该如何往下进行的时候,博主于是去餐厅吃了午饭,然后回来午休了一会,在醒来坐在床上发呆的时候,博主突然灵光一现脑子里莫名冒出了这样的一个名词:运行栈。运行栈的概念是在博主大二学C++的时候接触的,现在也只是模糊的有这个概念,于是马上翻到到运行栈的地方重温了一遍,【关于运行栈的概念博主会在文章最下边给出简单介绍,读者也可自行查阅资料】,总感觉要抓住了些什么但还是很渺茫,那个纠结啊,不说了~~。相信大家都清楚栈的概念,而函数调用的过程就是一个压栈弹出的过程,有了这个方向【函数调用】,结合上边的运行结果,楼主进行了如下分析:
package com.zhu.test; import java.text.SimpleDateFormat; public class finallyDemo2 { public static void main(String[] args) { System.out.println("main: result = "+test()); } private static int test() { try{ System.out.println("test's try block."); return fun1(); }finally{ System.out.println("test's finally block."); return fun2(); } } private static int fun1(){ System.out.println("Call fun1()."); return 1; } private static int fun2(){ System.out.println("Call fun2()"); return 2; } }
运行结果如下: 下面结合运行结果和运行栈的概念来分析一下函数调用过程,(原谅图画的不好,画成这样已经很难为自己了^_^,小伙伴们凑合着看吧。【大家跟着调用过程的箭头看就一目了然了】
由图可以分析,在finally语句块调用fun2()函数后,fun2()返回给finally的值为2,接着注意了,这次主函数接受到的是2是因为finally语句块直接return,跳过了try语句块中的return语句,就是说,这次test()函数的终止是在finally语句块中,而没有再次经过try语句块的return,也可以说finally中的return将try中return语句在内存中开辟的返回通道给短路了,所以主函数接受到的是finally语句返回的2。也可以这样理解:主函数调用子函数并得到结果的过程,好比主函数准备一个空罐子,当子函数要返回结果时(即执行return语句时),先把结果(return后的返回值)放到罐子里,然后再将程序逻辑返回到主函数(之前还是要执行finally代码块的)。
总结:【经过多出询问和搜索得出的比较靠谱结果是:在执行try代码块中的return语句时,会有一个临时变量或临时地址空间记录return后的返回值(也可以说是在内存中已经开辟了一个返回的通道并将return后的值放进去),然后才会去执行finally代码块,而finally中对x值的改变不会影响已经赋值的临时变量,至于是否在执行finally代码块时已经将临时变量返回给主函数还有待考证】
(读者也可以参阅:http://www.cnblogs.com/forcertain/archive/2012/11/22/2782855.html
http://javcoder.iteye.com/blog/1131003中的内容)
【ps:运行栈的概念:运行栈实际上是一段区域的内存空间,与存储全局变量的空间无异,只是寻址的方式不同而已。运行栈中的数据分为一个一个的栈帧,每个栈帧对应一次函数调用,栈帧中包括这次函数调用的形参值,一些控制信息,局部变量和临时数据(例如复杂表达式计算的中间值,某些函数的返回值)。每次发生函数调用时,都会有一个栈帧被压入运行栈中,而调用返回后,相应的栈帧会被弹出。....】