顽强的的砂锅之——深究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 ...
随机推荐
- @property(nonatomic) UIViewAutoresizing autoresizingMask;
在 UIView 中有一个autoresizingMask的属性,它对应的是一个枚举的值(如下),属性的意思就是自动调整子控件与父控件中间的位置,宽高. 1 2 3 4 5 6 7 8 9 enum ...
- win8.1点击“更改电脑设置”无反应(闪退)
系统:win8.1 专业版 症状:win键+C → 设置 → 更改电脑设置,无反应. 尝试办法: 1.SFC /scannow扫描修复,扫描出错误但无法修复.因为曾经为了节省空间,用DISM++清理了 ...
- QT修改默认的滚动条样式
这几天写一个类似于悬浮窗的小程序,可是qt自带的滚动条实在难看,经过多番查找终于找到一个类似于qq聊天窗口的滚动条,废话不说上代码.希望能帮到大家 1.写入到文件中,新建个xx.qss,然后复制一下内 ...
- 学习笔记——工厂模式Factory
Product是我们所需要获得的对象,一般的使用中,我们直接通过new获得新对象. 当需要大量获得对象时,每一次都new会很麻烦. <真菌世界>游戏中,一个星球上有很多树,一棵树会不断生成 ...
- android 5.0
google 2014开发大会发布了全新的设计语言Material Design,并计划将其应用到Android.Chrome OS和网页等平台上,而最新发布的Android L系统就采用了这种设计语 ...
- 10、桥接模式(Bridge)
桥接模式就是把事物和其具体实现分开,使他们可以各自独立的变化.桥接的用意是:将抽象化与实现化解耦,使得二者可以独立变化,像我们常用的JDBC桥DriverManager一样,JDBC进行连接数据库的时 ...
- android使用百度app分享,app统计出现的异常,FrontiaApplication类
想在app里加入百度分享和百度统计.查看了百度移动统计的文档后下载官网给的demo.参照demo给出的代码给自己的app加入代码.以上步骤比较简单.不细说,下面说下此过程需要注意的一些点,不然会引发异 ...
- docker私有仓库搭建(ubuntu 14.04和centos7)
最近是在做一个关于docker云化的项目,马上就要开始实战.下午先做了一个私有仓库搭建的实验,先大概做个笔记,有兴趣的蛮看一下吧. 先在所有机子上都安装上docker,我的是两台ubuntu,分别是1 ...
- ArrayList和数组间的相互转换
开发中不免碰到List与数组类型之间的相互转换,举一个简单的例子: package test.test1; import java.util.ArrayList; import java.util.L ...
- POJ 2240 Arbitrage Bellman_ford 判读是否存在正环
和POJ1860差不多,就是用bellmanford判读是否存在正环,注意的是同种货币之间也可以交换,就是说:A货币换A货币汇率是2的情况也是存在的. #include<stdio.h> ...