刚开始学习java的时候,finally,return是个头疼的问题,面试题里天天都是问谁先执行?
下面我们就结合《深入java虚拟机》第18章 finally子语句和例子对应的字节码来探讨一下这个问题
看此篇文章之前可以看看前面一篇文章描述finally字节码:
http://abc08010051.iteye.com/admin/blogs/2154981
例子1:
public static int tt1() {
int b = 23;
try {
System.out.println("yes");
return b += 88;
} catch (Exception e) {
System.out.println("error : " + e);
} finally {
if (b > 25) {
System.out.println("b>25 : " + b);
}
System.out.println("finally");
}
return b;
}
代码执行结果:
yes
b>25 : 111
finally
111
bytecode:
bipush 23 //把23压入栈
istore_0 //把23放到位置为0的局部变量中
getstatic java/lang/System/out Ljava/io/PrintStream;
ldc "yes" //把常量“yes”压入栈
invokevirtual java/io/PrintStream/println(Ljava/lang/String;)V //打印"yes"
iinc 0 88 //位置为0的变量值加88
iload_0 //把位置为0的局部变量(值为111)压入栈
istore_1 //把栈顶元素出栈,(栈顶元素为111),并赋值给位置为1的局部变量
iload_0 //把位置为0的局部变量值压入栈
bipush 25 //把25压入栈
if_icmple 22 //比较栈顶两个int类型的值,如果小于0调到22执行
getstatic java/lang/System/out Ljava/io/PrintStream; //此行到22行,通过StringBuilder构建字符串"b>25 : "加上b(即位置为0的局部变量值),并打印字符串
new java/lang/StringBuilder
dup
invokespecial java/lang/StringBuilder/()V
ldc "b>25 : "
invokevirtual java/lang/StringBuilder/append(Ljava/lang/String;)Ljava/lang/StringBuilder;
iload_0
invokevirtual java/lang/StringBuilder/append(I)Ljava/lang/StringBuilder;
invokevirtual java/lang/StringBuilder/toString()Ljava/lang/String;
invokevirtual java/io/PrintStream/println(Ljava/lang/String;)V //从11行到此行,通过StringBuilder构建字符串"b>25 : "加上b(即位置为0的局部变量值),并打印字符串
getstatic java/lang/System/out Ljava/io/PrintStream;
ldc "finally"
invokevirtual java/io/PrintStream/println(Ljava/lang/String;)V //打印“finally”字符串
iload_1 //获取位置为1的局部变量的值并压入栈中
ireturn //返回栈顶的int值
astore_1
getstatic java/lang/System/out Ljava/io/PrintStream;
new java/lang/StringBuilder
dup
invokespecial java/lang/StringBuilder/()V
ldc "error : "
invokevirtual java/lang/StringBuilder/append(Ljava/lang/String;)Ljava/lang/StringBuilder;
aload_1
invokevirtual java/lang/StringBuilder/append(Ljava/lang/Object;)Ljava/lang/StringBuilder;
invokevirtual java/lang/StringBuilder/toString()Ljava/lang/String;
invokevirtual java/io/PrintStream/println(Ljava/lang/String;)V
iload_0
bipush 25
if_icmple 51
getstatic java/lang/System/out Ljava/io/PrintStream;
new java/lang/StringBuilder
dup
invokespecial java/lang/StringBuilder/()V
ldc "b>25 : "
invokevirtual java/lang/StringBuilder/append(Ljava/lang/String;)Ljava/lang/StringBuilder;
iload_0
invokevirtual java/lang/StringBuilder/append(I)Ljava/lang/StringBuilder;
invokevirtual java/lang/StringBuilder/toString()Ljava/lang/String;
invokevirtual java/io/PrintStream/println(Ljava/lang/String;)V
getstatic java/lang/System/out Ljava/io/PrintStream;
ldc "finally"
invokevirtual java/io/PrintStream/println(Ljava/lang/String;)V
goto 74
astore_2
iload_0
bipush 25
if_icmple 69
getstatic java/lang/System/out Ljava/io/PrintStream;
new java/lang/StringBuilder
dup
invokespecial java/lang/StringBuilder/()V
ldc "b>25 : "
invokevirtual java/lang/StringBuilder/append(Ljava/lang/String;)Ljava/lang/StringBuilder;
iload_0
invokevirtual java/lang/StringBuilder/append(I)Ljava/lang/StringBuilder;
invokevirtual java/lang/StringBuilder/toString()Ljava/lang/String;
invokevirtual java/io/PrintStream/println(Ljava/lang/String;)V
getstatic java/lang/System/out Ljava/io/PrintStream;
ldc "finally"
invokevirtual java/io/PrintStream/println(Ljava/lang/String;)V
aload_2
athrow
iload_0
ireturn//对应代码最后的return
上面的字节码值解释了我们方法正常执行的部分,由上面的字节码解释我们知道:return语句时在finally例程执行之后才执行的
例子2:
这个例子和上面的那个差不多,差别是在finally语句块里对变量进行了操作:
public static int tt2() {
int b = 23;
try {
System.out.println("yes");
return b += 88;
} catch (Exception e) {
System.out.println("error : " + e);
} finally {
if(b>25)
{
System.out.println("b>25 : "+b);
}
System.out.println("finally");
b = 100;
}
return b;
}
执行结果:
yes
b>25 : 111
finally
111
这个结果很令人诧异,如果按照例子1得出来的结论,return 在finally语句块之后执行,那么输出结果应该是:
yes
b>25 : 111
finally
100
这是为什么呢?
我们来看看tt2()方法的字节码:
bipush 23 //把23压入栈
istore_0 //把23放到位置为0的局部变量中
getstatic java/lang/System/out Ljava/io/PrintStream;
ldc "yes" //把常量“yes”压入栈
invokevirtual java/io/PrintStream/println(Ljava/lang/String;)V //打印"yes"
iinc 0 88 //位置为0的变量值加88
iload_0 //把位置为0的局部变量(值为111)压入栈
istore_1 //把栈顶元素出栈,(栈顶元素为111),并赋值给位置为1的局部变量
iload_0 //把位置为0的局部变量值压入栈
bipush 25 //把25压入栈
if_icmple 22 //比较栈顶两个int类型的值,如果小于0调到22执行
getstatic java/lang/System/out Ljava/io/PrintStream; //此行到22行,通过StringBuilder构建字符串"b>25 : "加上b(即位置为0的局部变量值),并打印字符串
new java/lang/StringBuilder
dup
invokespecial java/lang/StringBuilder/()V
ldc "b>25 : "
invokevirtual java/lang/StringBuilder/append(Ljava/lang/String;)Ljava/lang/StringBuilder;
iload_0
invokevirtual java/lang/StringBuilder/append(I)Ljava/lang/StringBuilder;
invokevirtual java/lang/StringBuilder/toString()Ljava/lang/String;
invokevirtual java/io/PrintStream/println(Ljava/lang/String;)V //从11行到此行,通过StringBuilder构建字符串"b>25 : "加上b(即位置为0的局部变量值),并打印字符串
getstatic java/lang/System/out Ljava/io/PrintStream;
ldc "finally"
invokevirtual java/io/PrintStream/println(Ljava/lang/String;)V //打印“finally”字符串
bipush 100 //把100压入栈顶
istore_0 //把栈顶值(100)弹出栈,并赋值给位置为0的局部变量(代码中的b)
iload_1 //把位置为1的局部变量(111)压入栈
ireturn //弹出栈顶的int值,返回,退出方法
astore_1
getstatic java/lang/System/out Ljava/io/PrintStream;
new java/lang/StringBuilder
dup
invokespecial java/lang/StringBuilder/()V
ldc "error : "
invokevirtual java/lang/StringBuilder/append(Ljava/lang/String;)Ljava/lang/StringBuilder;
aload_1
invokevirtual java/lang/StringBuilder/append(Ljava/lang/Object;)Ljava/lang/StringBuilder;
invokevirtual java/lang/StringBuilder/toString()Ljava/lang/String;
invokevirtual java/io/PrintStream/println(Ljava/lang/String;)V
iload_0
bipush 25
if_icmple 53
getstatic java/lang/System/out Ljava/io/PrintStream;
new java/lang/StringBuilder
dup
invokespecial java/lang/StringBuilder/()V
ldc "b>25 : "
invokevirtual java/lang/StringBuilder/append(Ljava/lang/String;)Ljava/lang/StringBuilder;
iload_0
invokevirtual java/lang/StringBuilder/append(I)Ljava/lang/StringBuilder;
invokevirtual java/lang/StringBuilder/toString()Ljava/lang/String;
invokevirtual java/io/PrintStream/println(Ljava/lang/String;)V
getstatic java/lang/System/out Ljava/io/PrintStream;
ldc "finally"
invokevirtual java/io/PrintStream/println(Ljava/lang/String;)V
bipush 100
istore_0
goto 80
astore_2
iload_0
bipush 25
if_icmple 73
getstatic java/lang/System/out Ljava/io/PrintStream;
new java/lang/StringBuilder
dup
invokespecial java/lang/StringBuilder/()V
ldc "b>25 : "
invokevirtual java/lang/StringBuilder/append(Ljava/lang/String;)Ljava/lang/StringBuilder;
iload_0
invokevirtual java/lang/StringBuilder/append(I)Ljava/lang/StringBuilder;
invokevirtual java/lang/StringBuilder/toString()Ljava/lang/String;
invokevirtual java/io/PrintStream/println(Ljava/lang/String;)V
getstatic java/lang/System/out Ljava/io/PrintStream;
ldc "finally"
invokevirtual java/io/PrintStream/println(Ljava/lang/String;)V
bipush 100
istore_0
aload_2
athrow
iload_0
ireturn
从上面的字节码可以看出,return还是在finally语句块之后执行, 返回111而不是100的原因可以参考上面标注为红色的bytecode解释,
当代码执行到“return b += 88;” 这一行时,jvm把局部变量b加88,加后的局部变量b存放在位置为0的变量中,然后把b的值复制存放到位置为1的局部变量中,
执行finally语句块时,对局部变量b进行操作,在虚拟机内部只是对位置为0的变量进行操作,当返回时,虚拟机返回的是位置为1的变量值,即为111
深入java虚拟机也有此解释:
例子3:
public static int tt3() {
int b = 23;
try {
System.out.println("yes");
return b += 88;
} catch (Exception e) {
System.out.println("error : " + e);
} finally {
if(b>25)
{
System.out.println("b>25 : "+b);
}
System.out.println("finally");
b = 100;
return b;
}
}
执行结果:
yes
b>25 : 111
finally
100
根据结果判断,在执行“return b += 88;”这行代码的时候,把表达式“b += 88;”执行以后去执行finally语句块执行,在finally语句中return,退出方法