问题
在Java的异常体系中,我们经常会使用finally语句块来保证进行一些无论有无异常都要执行的处理流程,但finally语句块与return语句究竟哪个先执行总是让人迷惑。根据书本介绍,似乎是finally先于return执行,但原理又是什么呢?
分析
请看下面这段代码,考虑test方法的返回值在出现异常和不出现异常的情况下分别是多少?
static int test() {
int x;
try {
x = 1;
return x;
} catch (Exception e) {
x = 2;
return x;
} finally {
x = 3;
}
}
稍有经验的Java程序都可以看出:如果没有异常,则返回1;如果出现了Exceptioin异常,则返回2;如果出现了Exception以外的异常,则方法非正常退出,没有返回值。
下面我们使用javap命令将编译得到的class文件反汇编,看一下我们的示例程序生成的字节码是什么样子的,进而对其分析。
其中test方法部分的字节码如下所示:
static int test();
Code:
Stack=1, Locals=4, Args_size=0
0: iconst_1// 将int类型的1压至栈顶
1: istore_0// 将栈顶int型数据存入第一个本地变量; 即try块中的x=1
2: iload_0// 将第一个int型本地变量压至栈顶
3: istore_3// 将栈顶int型数值存入第四个本地变量(最后一个)
4: iconst_3// 将int型3压至栈顶
5: istore_0// 将栈顶int型数据存入第一个本地变量; 即finally块中的x=3
6: iload_3// 将第四个int型本地变量压至栈顶
7: ireturn// 将栈顶int型数值返回
8: astore_1// 将栈顶引用型数值存入第二个本地变量
9: iconst_2// 将int类型的2压至栈顶
10: istore_0// 将栈顶int型数据存入第一个本地变量; 即catch块中的x=2
11: iload_0// 见第2行
12: istore_3// 见第3行
13: iconst_3// 见第4行
14: istore_0// 见第5行
15: iload_3// 见第6行
16: ireturn// catch语句块中的return
17: astore_2// try中出现不属于Exception或其子类的异常或者catch中出现异常;将异常存入第3个本地变量
18: iconst_3// finally中的x=3
19: istore_0
20: aload_2// 将第3个本地变量中的异常压入栈顶
21: athrow// 抛出异常
Exception table:
from to target type
0 4 8 Class java/lang/Exception
0 4 17 any
8 13 17 any
首先看一下方法的异常表信息,也就是字节码中的Exception table部分。异常表中生成了三条记录,对应三条可能出现的代码执行路径:
第0至4行出现Exception异常,则程序跳到第8行开始执行;对应try语句块中出现属于Exception或其子类异常,则转到catch语句块处理。
第0至4行出现不属于Exception或其子类的异常,程序跳到第17行处理;对应try语句块中出现不不属于Exception或其子类的异常,程序跳到finally语句 块处理。
第8到13行出现任何异常,程序跳到第17行处理;对应catch语句块中出现任何异常,程序转到finally语句块处理。
如字节码中的注释所示,字节码中第0至4行(不包括第4行)所做的操作就是将整数1赋值给变量x,并且将此时x的值复制一份到最后一个本地变量表的Slot中(该Slot里的值在ireturn指令执行前会被重新读取并压至操作数栈顶,作为方法返回值来使用。这里将Slot标记为returnValue)。如果这时没有发生异常,程序继续往下运行第4至7行,也就是finally语句块中的代码,在这里将x赋值为3,然后将之前保存在returnValue(最后一个Slot)中的整数1读入到操作数栈顶,最后ireturn指令会以int形式返回操作数栈顶中的值,方法结束。
如果在try语句块中出现了Exception异常,程序跳到第8行。第8到13行所做的事情就是将2赋值给变量x,然后将x此时的值复制一份给returnValue(第12行),然后执行finally语句块将变量x的改为3(第13、14行)。方法返回前(ireturn指令,第16行)同样是将returnValue中存储的整数2读到操作栈顶以int形式返回,方法结束。
第17行之后的代码是在上面三条异常表记录中后两条情况下要处理的字节码,也就是在catch语句块中出现异常或者在try语句块中出了不属于Exception或其子类异常的情况下。所做的操作也就是将变量x的值改为3,并将异常对象的引用压入操作栈顶并抛出,方法结束。
结论
finally中的语句在任何情况下都要处理,从上面字节码可以看到,三条可能流程都有finally子句对应的指令(分别为第4、5行;第13、14行;以及第18、19行);
finally是在return语句之前执行的,第7行和16行分别对应try和catch中的return语句,都在finally对应的字节码指令之后可以证明;
finally语句中对返回值的修改并不会真正被返回。以catch中的return为例,在执行finally字节码指令前(第3行),将返回值存入本地变量最后一个Slot(简称为returnValue)中,而finally语句只是改变了局部变量x的值(第4、5行),并没有改变returnValue中的值,然后在执行ireturn指令前,将returnValue中的值压至栈顶(第6行)),ireturn指令将其返回(第7行)。