详解Java中finally代码块与return执行顺序的关系

提出问题:

我们都知道这样一句话,java异常处理的try_catch_finally逻辑中的finally是一定会被执行的,并且即使该逻辑中出现了return语句,那么finally仍旧是要在return返回之前执行的。

其实,上述说法,并没有逻辑错误,但是,其含义表达很笼统,这里就详细探究一下这个问题。

1.finally真的一定会被执行吗?

问题的答案,很简单,不一定。(这可能颠覆了你之前看书的认知,但是,事实的确如此)

(1)首先明确,finally并不是一个单独的代码执行逻辑,他是必须依附于try代码块的,那就很明显,如果代码逻辑,连try都无法进入,又谈何finally一定会被执行呢?

(2)那就是,java中存在代码显式的控制虚拟机的运行,也就是说程序可以显式的强制终止JVM,System.exit(0)是终止Java虚拟机JVM的,那么如果try中出现了这样的语句,JVM都停止了,又怎么会处理finally呢?

所以说,这句话,细致来说,应该是:finally在try代码块正常被进入执行,jvm正常执行处理的情况下,是一定会被执行的。

2.如果try中出现了return语句,finally也会执行,但是执行的顺序是怎样的?

稍有经验的java编程者会马上自信的给出一个答案,那就是finally在return之前执行。

其实这样的回答,并没有错误,但是,我相信这样回答的人,其实根本不理解,这种情况下,真正的执行逻辑。

因为,严格意义上来说,这种回答,并不严谨,甚至是错误的。(可能又颠覆了你的认知)

现在,收起你那冲动的心,我们一起来进行代码测试:

public class Test
{
	public static void main(String args[])
	{
		System.out.println("方法return返回结果为: "+new Test().test());
	}
	
	public int test()
	{
		int x = 1;    //方法局部变量x,整形,初始值为1
		try {
			return x = 2;    //try中出现retrun语句
		} catch (Exception e) {
			return x;        //catch中出现return语句
		}finally{
			x = 3;    //finally执行逻辑
                        System.out.println("finally执行后的x值为: "+x);
		}
	}
}

那么,执行结果是什么呢?

哇塞,输出的结果显示的x的返回值是2,此时你是否在想:的确啊,finally执行完,return执行,返回值x就是2啊!

如果你是这样想的,那么你把try中的return x=2改为return x。你会发现,世界崩塌了,哈哈!

那么,干货来了,让我们一起来看看,这到底是怎么回事:

首先,解释一下相关的基础知识:

(1)关于Java的JVM中的内存模型,这里只是涉及到虚拟机栈。

虚拟机栈,线程私有,栈帧是其基本单位!描述的是java方法执行时的内存模型!(有兴趣的你,强烈建议拜读一下前辈的《深入理解JAVA虚拟机》一书)。执行方法的线程会为每一个方法分配一小块栈空间来作为该方法执行时的内存空间,栈幀分为三个区域:

 ①操作数栈,用来保存正在执行的表达式中的操作数(仔细看下面,就明白了这句话的含义了)
 ②局部变量区,用来保存方法中使用的变量,包括方法参数,方法内部声明的变量,以及方法中使用到的对象的成员变量或类的成员变量(静态变量),最后两种变量会复制到局部变量区,因此在多线程环境下,需要保持同步。
 ③字节码指令区,这个不用解释了,就是方法中的代码翻译成的指令

(2)关于return语句的深入理解:


①return语句的格式如下:
                 return      [expression];
    其中expression(表达式)是可选(也就是所谓的,可有可无)的,因为有些方法没有返回值(返回值是void),所以return后面     也就没有表达式,或者可以看做是空的表达式。


②我们知道return语句的作用可以结束方法并返回一个值,那么他返回的是哪里的值呢?

返回的是return指令执行的时刻,操作数栈顶的值,不管expression是一个怎样的表达式,究竟做了些什么工作,对于return指令来说都不重要,他只负责把操作数栈顶的值返回。
    而return expression是分成两部分执行的:
    执行:expression;
    执行:return指令;
    例如:return x+y;
    这句代码先执行x+y,再执行return;首先执行将x以及y从局部变量区复制到操作数栈顶的指令,然后执行加法指令,这个时候结果x+y的值会保存在操作数栈的栈顶,最后执行return指令,返回操作数栈顶的值。
    对于return x;先执行x,x也是一个表达式,这个表达式只有一个操作数,会执行将变量x从局部变量区复制到操作数栈顶的指令,然后执行return,返回操作数栈顶的值。因此return x实际返回的是return指令执行时,x在操作数栈顶的一个快照或者叫副本,而不是x这个值。

好了,有了上述描述总结之后,我们可以来分析,我们的核心问题了。

也就是,try中存在return,那么finally的执行逻辑和return语句两者的执行顺序究竟是怎样的?

 1、执行:expression,计算该表达式,结果保存在操作数栈顶;

 2、执行:操作数栈顶值(expression的结果)复制到局部变量区作为返回值;

 3、执行:finally语句块中的代码;

 4、执行:将第2步复制到局部变量区的返回值又复制回操作数栈顶;

 5、执行:return指令,返回操作数栈顶的值;

现在的你是否有些迷茫,这说的是啥啊?这作者是在干嘛啊,哈哈!

不急,我们还是用例子中的代码一步一步的剖析整个过程:

首先,try中遇到了return语句,并且有依附于try代码块的finally执行逻辑。按照上述的执行顺序,我们来理解。

①计算return后的表达式,也就是 x=2,这条语句的本质是,从局部变量表取出局部变量x,放到操作数栈中,执行赋值操作=2,产生的结果就是,操作数栈顶有一个x,此时值为2。

②如果,没有finally,那么按照return的逻辑,直接把栈顶的x放回出去即可,但是,虚拟机看到了有finally的存在,那么就需要转身去处理finally,那么,重点来了,刚才return后表达式的计算结果怎么办呢?其实,这个问题才是整个问题的核心。此时虚拟机很聪明,就像多线程处理似的(只是举个例子,类似于线程间的切换,但是两者毫无关联,千万不要纠结在此),他保存当前的return后面表达式的执行结果,然后去执行finally。也就是所谓的把刚才计算的操作栈栈顶的值放回局部变量表,用于保存,转身再去处理finally的代码逻辑。

③执行finally中的代码逻辑,也就是x=3,也就是取出局部变量表的x放到操作栈中,执行x=3,也就是正常执行finally中的代码逻辑,其实这一步,局部变量表中的x已经被置为3了。

④虚拟机执行完finally后,在此切换为之前try中的return,这时候,就需要执行return语句的第二步,即返回操作数栈顶,那么此时操作数栈顶的值是多少呢?其实,看上面的步骤总结,也知道,虚拟机在执行玩finally返回到return的时候,他会取出之前所保存的return中表达式的执行结果,这很容易理解,我return返回的是我自己后面表达式的结果,也就是把之前保存在局部变量表中的执行结果再次拿到栈顶,所以,栈顶此时又变成了2。

⑤return语句返回,返回栈顶x=2.整个方法结束,栈帧释放。

相信这时候的你,如果仔细的思考了上述过程,已经能明白其中的本质了。

其实很简单的总结,finally的执行时间是:retrun表达式执行之后,在return返回操作之前。

 

3.那么假如return在finally代码块中呢?又是怎样的呢?

其实这个问题,完全是一个傻问题,也就是根本就不是一个问题。

为什么呢?我们之所以讨论try中的return和finally的执行顺序问题,是因为两者存在一定的模糊矛盾,return是要返回函数,也就是所谓的方法出口,而finally又必须执行,所以才有了上述大篇幅的讨论研究。

那如果,finally中存在return,何来矛盾呢?表现出来的代码逻辑正常执行,不就可以了吗?(哈哈,你是不是笑了?)
 

此时,如果被问到相关的问题,内心是不是这样的?

都走开,老子要装b了!哈哈

猜你喜欢

转载自blog.csdn.net/romantic_jie/article/details/100065632