对于finally的作用相关内容不再陈述,网上有很多
本博文着重分析finally块中程序到底在return之前执行还是return之后执行?
没有了解过JVM指令的可以看这篇博客:JVM操作指令集解析
先看一个程序示例:
public class AtomicDemo{
public static int test(int i){
try {
i=9;
return i;
} finally {
i=10;
}
}
public static void main(String[] args) {
System.out.println(test(12));
}
}
输出结果:
9
分析:
- 仅从控制台输出结果上看好像是在return之后执行, 因为i的值是9, 没有被修改为10;
- 但是, 事实上是在return之前执行的, 并且在finally块执行结束之后i的值是已经被修改为10了
- 可以这样理解:return 语句1; finally块是在语句1执行之后, return执行之前执行的;
反编译后的结果:
Compiled from "AtomicDemo.java"
public class com.lic.atomic.AtomicDemo {
public com.lic.atomic.AtomicDemo();
Code:
0: aload_0
1: invokespecial #8 // Method java/lang/Object."<init>":()V
4: return
public static int test();
Code:
0: bipush 9 //将一个8位带符号整数压入栈 (将int类型常量9放入操作数栈中)
2: istore_0 //将int类型值存入局部变量0 (将局部变量0放入局部变量表,然后将int类型常量9从操作数栈中取出,存入局部变量0中)
3: iload_0 //从局部变量0中装载int类型值 (将局部变量0中的值9压入操作数栈,此步骤是准备将i(9)返回 --> return i; 需要先将9压入操作数栈,然后返回)
4: istore_2 //将int类型值存入局部变量2 (将上一步中已经加载到操作栈中准备返回的值9暂存于局部变量2中; 之所以是暂存,是因为在finally块执行完之后,会将局部变量2中的值返回)
5: bipush 10 //将一个8位带符号整数压入栈 (将int类型常量10放入操作数栈中)
7: istore_0 //将int类型值存入局部变量0 (将局部变量0放入局部变量表,然后将int类型常量10从操作数栈中取出,存入局部变量0中; 此时i的值已经被修改为10)
8: iload_2 //从局部变量2中装载int类型值 (将局部变量2中的值9压入操作数栈)
9: ireturn //从方法中返回int类型的数据 (将操作数栈栈顶元素弹出,返回)
10: astore_1 //将引用类型或returnAddress类型值存入局部变量1
11: bipush 10 //将一个8位带符号整数压入栈
13: istore_0 //将int类型值存入局部变量0
14: aload_1 //从局部变量1中装载引用类型值
15: athrow //抛出异常或错误
Exception table:
from to target type
0 5 10 any
public static void main(java.lang.String[]);
Code:
0: getstatic #23 // Field java/lang/System.out:Ljava/io/PrintStream;
3: invokestatic #29 // Method test:()I
6: invokevirtual #31 // Method java/io/PrintStream.println:(I)V
9: return
}
分析:
- 在步骤3中, 将 i 的值 9 压入操作数栈,准备返回; 然后发现需要执行finally块中的数据, 因此jvm将准备返回的值 9 存入局部变量2中, 接下来去执行finally中的程序, 将 i 的值修改为10;
- 在finally块中的程序执行结束之后将局部变量2中的数据重新压入操作数栈中, 然后返回, 所以返回的值为 9 ;
- 但是此时 i 的值已经被修改为10
如果在finally块中被修改的数据时一个对象会怎么样呢?
程序示例:
public class AtomicDemo3 {
public static StringBuffer test(){
StringBuffer sb = new StringBuffer();
try {
sb.append("abc");
return sb;
} finally {
sb.append("123");
}
}
public static void main(String[] args) {
System.out.println(test().toString());
}
}
输出结果:
abc123
反编译结果:
Compiled from "AtomicDemo3.java"
public class com.lic.atomic.AtomicDemo3 {
public com.lic.atomic.AtomicDemo3();
Code:
0: aload_0
1: invokespecial #8 // Method java/lang/Object."<init>":()V
4: return
public static java.lang.StringBuffer test();
Code:
0: new #16 // class java/lang/StringBuffer
3: dup
4: invokespecial #18 // Method java/lang/StringBuffer."<init>":()V
7: astore_0
8: aload_0
9: ldc #19 // String abc
11: invokevirtual #21 // Method java/lang/StringBuffer.append:(Ljava/lang/String;)Ljava/lang/StringBuffer;
14: pop
15: aload_0 //从局部变量0中装载引用类型值 (将对象sb的地址引用加载到操作栈准备返回)
16: astore_2 //将引用类型或returnAddress类型值存入局部变量2 (将对象sb的地址引用暂存于局部变量2中,等finally中的程序执行结束之后返回)
17: aload_0 //从局部变量0中装载引用类型值 (将对象sb的地址引用加载到操作栈中准备执行append()方法)
18: ldc #25 // String 123
20: invokevirtual #21 // Method java/lang/StringBuffer.append:(Ljava/lang/String;)Ljava/lang/StringBuffer;
23: pop
24: aload_2 //从局部变量2中装载引用类型值 (局部变量2中的对象sb的地址引用加载到操作栈准备返回)
25: areturn //从方法中返回引用类型的数据
26: astore_1
27: aload_0
28: ldc #25 // String 123
30: invokevirtual #21 // Method java/lang/StringBuffer.append:(Ljava/lang/String;)Ljava/lang/StringBuffer;
33: pop
34: aload_1
35: athrow
Exception table:
from to target type
8 17 26 any
public static void main(java.lang.String[]);
Code:
0: getstatic #34 // Field java/lang/System.out:Ljava/io/PrintStream;
3: invokestatic #40 // Method test:()Ljava/lang/StringBuffer;
6: invokevirtual #42 // Method java/lang/StringBuffer.toString:()Ljava/lang/String;
9: invokevirtual #46 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
12: return
}
总结:
- finally块中的程序是在return之前执行的;
- 对于基本类型, 返回的结果是之前执行finally块程序之前的,因为jvm将执行finally块之前要返回的数据暂存于另一个变量B中了, 等执行finally块之后再将B中的值加载到操作数栈中返回;
- 对于对象类型, 在执行finally块前后返回的都是该对象的地址引用, 所以可以看到finally块执行后的结果;