顽强的的砂锅之——深究finally代码块与return语句的执行顺序!
当问到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:运行栈的概念:运行栈实际上是一段区域的内存空间,与存储全局变量的空间无异,只是寻址的方式不同而已。运行栈中的数据分为一个一个的栈帧,每个栈帧对应一次函数调用,栈帧中包括这次函数调用的形参值,一些控制信息,局部变量和临时数据(例如复杂表达式计算的中间值,某些函数的返回值)。每次发生函数调用时,都会有一个栈帧被压入运行栈中,而调用返回后,相应的栈帧会被弹出。....】
顽强的的砂锅之——深究finally代码块与return语句的执行顺序!的更多相关文章
- ASP.NET MVC 5 05 - 视图
PS: 唉,这篇随笔国庆(2015年)放假前几天开始的,放完假回来正好又赶上年底,公司各种破事儿. 这尼玛都写跨年了都,真服了.(=_=#) 好几次不想写了都. 可是又不想浪费这么多,狠不下心删除.没 ...
- JavaScript闭包之“词法作用域”
大家应该写过下面类似的代码吧,其实这里我想要表达的是有时候一个方法定义的地方和使用的地方会相隔十万八千里,那方法执行时,它能访问哪些变量,不能访问哪些变量,这个怎么判断呢?这个就是我们这次需要分析的问 ...
- 对Verilog 初学者比较有用的整理(转自它处)
*作者: Ian11122840 时间: 2010-9-27 09:04 ...
- 用Java实现非阻塞通信
用ServerSocket和Socket来编写服务器程序和客户程序,是Java网络编程的最基本的方式.这些服务器程序或客户程序在运行过程中常常会阻塞.例如当一个线程执行ServerSocket的acc ...
- Python3 与 C# 扩展之~基础衍生
本文适应人群:C# or Python3 基础巩固 代码裤子: https://github.com/lotapp/BaseCode 在线编程: https://mybinder.org/v2/g ...
- Java面向对象----个人参考资料
Java面向对象 :什么是面向对象.类与对象.封装.构造方法.static关键字.继承.抽象类.接口.多态 一.什么是面向对象 1.面向过程思想 面向过程:(PO,Procedure Oriented ...
- Scala学习(二)--- 控制结构和函数
控制结构和函数 摘要: 本篇主要学习在Scala中使用条件表达式.循环和函数,你会看到Scala和其他编程语言之间一个根本性的差异.在Java或C++中,我们把表达式(比如3+4)和语句(比如if语句 ...
- UI:多线程 、用GCD创建线程
什么是应用(程序):就是我们编写的代码编译后生成的app文件 进程:凡是一个运行的程序都可以看作为一个进程,如打开的多个 word,就可以认为是一个进程的多个线程. 线程:至少有一个线程就是主线程,网 ...
- egon说一切皆对象--------面向对象进阶紫禁之巅
一.检查isinstance(obj,cls)和issubclass(sub,super) class Foo(object): pass obj = Foo() isinstance(obj, Fo ...
随机推荐
- zend frameword 基本语法
#resources.frontController.moduleDirectory = APPLICATION_PATH "/modules"#resources.frontCo ...
- [转]intent 传递对象
---恢复内容开始--- Activity之间通过Intent传递值,支持基本数据类型和String对象及它们的数组对象byte.byte[].char.char[].boolean.boolean[ ...
- HDU1969:Pie(二分)
Pie Time Limit : 5000/1000ms (Java/Other) Memory Limit : 65536/32768K (Java/Other) Total Submissio ...
- Django: 之Model、Cookis、Session
到目前为止,当我们的程序涉及到数据库相关操作时,我们一般都会这么搞: 创建数据库,设计表结构和字段 使用MySQLdb来连接数据库,并编写数据访问层代码 业务逻辑层去调用数据访问层执行数据库操作 im ...
- HTML5来了:5个好用的混合式App开发工具
在残酷的移动互联网竞争环境下, HTML5技术一直受到各方关注,“HTML5颠覆原生 App”的争论也从未停止过,不管怎样HTML5生态的构建方兴未艾.不过对于移动开发者来说更关心的问题是如何低成本. ...
- JSP内置对象--web安全性及config对象的使用 (了解即可)
tomcat服务器配置的时候,在虚拟目录中必须存在一个WEB-INF文件夹,但是访问的时候并不能发现这个文件夹.改成WEB-INFs就可以看到. 所以WEB-INF文件夹不轻易让用户看到,那么其安全性 ...
- java thread park
http://agapple.iteye.com/blog/970055 apidoc中说,park/unpark用来阻塞/激活线程,但是没有弃用方法suspend/resume的缺点,suspend ...
- C++Builder 中动态数组的使用(转)
源:http://i.cnblogs.com/EditPosts.aspx?opt=1 和AnsiString类型一样,动态数组是为了和DELPHI中的动态数组相兼容而定义,在BCB中,动态数组是用模 ...
- struts1.x中web.xml文件的配置
1.配置欢迎文件清单 当客户访问Web应用时,如果仅仅给出Web应用的Root URL,没有指定具体的文件名.Web容器会自动调用Web应用的欢迎文件.<welcome-file-li ...
- UILabel 解析及自适应
CGFloat width1=[(NSString *)ob1 sizeWithFont:[UIFont systemFontOfSize:16] constrainedToSize:CGSizeMa ...