顽强的的砂锅之——深究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 ...
随机推荐
- 关于cin.getline和cin.get
<C++ Primer Plus(第六版)> P124 第8题 #include <iostream> using namespace std; struct Pizza ...
- ubuntu环境下安装Tomcat
tomcat 是javaweb开发的本地服务器,tomcat是目前比较流行的一款. 1.下载Tomcat:http://tomcat.apache.org 2.进入下载文件夹解压Tomcat:sudo ...
- elasticsearch 手动控制分片分布
elasticsearch可以通过reroute api来手动进行索引分片的分配. 不过要想完全手动,必须先把cluster.routing.allocation.disable_allocatio ...
- 【python之路10】python实例练习
#!usr/bin/env python # -*- coding:utf-8 -*- # 一.元素分类 # # 有如下值集合 [11,22,33,44,55,66,77,88,99,90...], ...
- 删除正在登录的SQL账号
exec sp_who '用户名' kill @spid sp_droplogin 用户名
- ZEN_CART_如何添加自定义页面
按照下面的路径,添加自己的文件,就OK了 以about us页面为例, 默认模板 \includes\templates\template_default\templates\tpl_about_us ...
- 为什么做Web开发要选择PHP
大部分互联网公司做WEb开发都选择PHP,PHP的优势在哪?你应该知道的 以前偶尔被人问到,为什么你(和大部分互联网公司)做Web开发要选择PHP, PHP有什么好处.简单的回答便是“PHP简单,开发 ...
- java开发第一天
今天是项目开始的时间,整体来说还是算顺利的.提前分好组,然后是听课时可以有人帮忙占座位的,感觉上是挺好的. 项目开发的难度看了看,由于有了第一次MFC开发的经验,所以这次听课感觉非常的有目标性,而且总 ...
- 为什么我的Android SDK Manager中只显示已安装的package?
如图.在菜单packages中,前两项我都是选了的 . 两种可能: 1. 明显是楼主连不上谷歌的更新服务器了,换成国内的吧,左上Tools-->Options弹出的界面有两个可以填写的 ,下面一 ...
- apache RewriteCond RewriteRule
http://www.rockbb.com/blog/?p=319 http://www.cnblogs.com/scgw/archive/2011/12/10/2283029.html 我的理解:当 ...