顽强的的砂锅之——深究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 ...
随机推荐
- OpenGL-------状态机
状态机就是一种存在于理论中的机器,它具有以下的特点: 1. 它有记忆的能力,能够记住自己当前的状态. 2. 它可以接收输入,根据输入的内容和自己的状态,修改自己的状态,并且可以得到输出. 3. 当它进 ...
- OpenGL---------光照的基本知识
从生理学的角度上讲,眼睛之所以看见各种物体,是因为光线直接或间接的从它们那里到达了眼睛.人类对于光线强弱的变化的反应,比对于颜色变化的反应来得灵敏.因此对于人类而言,光线很大程度上表现了物体的立体感. ...
- UICollectionView 浅析
什么是UICollectionView UICollectionView是一种新的数据展示方式,简单来说可以把他理解成多列的UITableView(请一定注意这是UICollectionView的最最 ...
- Loadrunner之文件的上传(八)
老猪提供: https://mp.weixin.qq.com/s?__biz=MzIwOTMzNDEwNw==&mid=100000013&idx=1&sn=624f5bc74 ...
- This compilation unit is not on the build path SVN
This compilation unit is not on the build path of a Java project 解决办法 把项目导入STS(基于Eclipse)时,项目出现问题, ...
- js zhi网马
大家对木马都不陌生了,它可能要算是计算机病毒史上最厉害的了,相信会使木马的人千千万万,但是 有很多人苦于怎么把木马发给对方,现在随着计算机的普及,在网络上我相信很少有人会再轻易的接收 对方的文件了 ...
- 把APP做成libary的注意事项
首先把build.gradle(app里的),里面改成这样 apply plugin: 'com.android.library'然后删掉applicationId这一行 注意,千万不能用注解,要把所 ...
- ThinkPHP 框架执行流程分析
总体来说,应用的流程涉及到几个文件:Index.phpThinkPHP.phpThink.class.phpApp.class.phpDispatcher.class.phpThinkPHP/Mode ...
- git bash退回上一个文件夹
cd ..\ a@w3311 MINGW32 /f/Projects/crm (master) $ cd..\ > bash: cd..: command not found a@w3311 M ...
- java.lang.ClassCastException: com.sun.proxy.$Proxy8 cannot be cast to com.bjsxt.service.UserServiceImpl01_AOP.
对于Spring AOP 采用两种代理方法,一种是常规JDK,一种是CGLIB,我的UserDao了一个接口IUserDao,当代理对象实现了至少一个接口时,默认使用 JDK动态创建代理对象,当代理对 ...