try,catch,finally在网上已经有很多人分析过了,我从字节码方面记录一下学习过程。
try{
...
return
}catch(Exception e){
...
return
}finally{
...
}
在执行try中return之前,会执行finally中的代码,之后执行try中return,如果finally中也有return,就会直接返回,不再执行try中的return.但执行了finally中的代码,也不一定会改变try中变量的值,看一下实例:
public static int inc(){
int x;
try{
x=1;
return x;
}catch (Exception e) {
x=2;
return x;
}finally{
x=3;
}
}
public static Map<String, String> getMap() {
Map<String, String> map = new HashMap<String, String>();
map.put("KEY", "INIT");
try {
map.put("KEY", "TRY");
return map;
}
catch (Exception e) {
map.put("KEY", "CATCH");
}
finally {
map.put("KEY", "FINALLY");
map = null;
}
return map;
}
这里有两个方法,inc()中是基本类型变量,getMap()中是引用类型变量,inc()中如果没有异常,x的值=1,如果发生了是Exception类型的异常,值为2,getMap()中最后map对象中"KEY"对应的value是"FINALLY",为什么第一个方法finally没有改变x的值,而第二个方法改变了map的值,来看一下这两个方法对应的字节码:
public static int inc();
Code:
Stack=1, Locals=4, Args_size=0
0: iconst_1 将常数1入栈
1: istore_0 将栈顶元素存入本地变量0(1)
2: iload_0 将本地变量0入栈
3: istore_3 存入本地变量3,进入finally前创建副本,此时变量3的值为1.
4: iconst_3 将常数3入栈
5: istore_0 将栈顶元素存到本地变量0中,此时本地变量0由1变为3.
6: iload_3 将本地变量3(1)入栈
7: ireturn 返回本地变量3的值,也就是1.
8: astore_1
9: iconst_2
10: istore_0
11: iload_0
12: istore_3
13: iconst_3
14: istore_0
15: iload_3
16: ireturn
17: astore_2
18: iconst_3
19: istore_0
20: aload_2
21: athrow
public static java.util.Map getMap();
Signature: length = 0x2
00 55
Code:
Stack=3, Locals=4, Args_size=0
0: new #86; //class java/util/HashMap
3: dup
4: invokespecial #88; //Method java/util/HashMap."<init>":()V
7: astore_0 将map的引用地址存到本地变量0
8: aload_0 本地变量0入栈,9-18是调用map.put方法存入(KEY,INIT)到map对象中。
9: ldc #50; //String KEY
11: ldc #89; //String INIT
13: invokeinterface #91, 3; //InterfaceMethod java/util/Map.put:(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
18: pop
19: aload_0 将本地变量0入栈(map的引用地址,供invokeinterface调用)
20: ldc #50; //String KEY 20-19是调用map.put方法存入(KEY,TRY)到map对象中
22: ldc #95; //String TRY
24: invokeinterface #91, 3; //InterfaceMethod java/util/Map.put:(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
29: pop
30: aload_0 将map的引用地址入栈
31: astore_3 将map的引用地址存到本地变量3,存了一个副本,此时map对象中是(key,try)
32: aload_0 本地变量0(map的引用地址)入栈
33: ldc #50; //String KEY 33-42是调用map.put方法存入(KEY,FINALLY)到map对象中
35: ldc #97; //String FINALLY
37: invokeinterface #91, 3; //InterfaceMethod java/util/Map.put:(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
42: pop
43: aconst_null 将常量null的引用地址入栈,
44: astore_0 存到本地变量0,覆盖map的引用地址,
45: aload_3 将变量3(map地址的副本)入栈
46: areturn 返回map的引用地址
47: astore_1
48: aload_0
49: ldc #50; //String KEY
51: ldc #99; //String CATCH
53: invokeinterface #91, 3; //InterfaceMethod java/util/Map.put:(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
58: pop
59: aload_0
60: ldc #50; //String KEY
62: ldc #97; //String FINALLY
64: invokeinterface #91, 3; //InterfaceMethod java/util/Map.put:(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
69: pop
70: aconst_null
71: astore_0
72: goto 91
75: astore_2
76: aload_0
77: ldc #50; //String KEY
79: ldc #97; //String FINALLY
81: invokeinterface #91, 3; //InterfaceMethod java/util/Map.put:(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
86: pop
87: aconst_null
88: astore_0
89: aload_2
90: athrow
91: aload_0
92: areturn
在JDK 1.4.2之后,编译器自动在每段可能的分支路径之后都将finally语句块的内容冗余生成一遍来实现finally语义。所以我只分析try和finally这一段,后面的类似。
可以看到inc()方法中行号为2,3,创建了一个x变量的副本存在本地变量表slot3中,之后虽然执行了finally语句中x=3的赋值,但保存在solt1,而return返回的slot3副本中的内容,所以x的值还是1。
getMap() 方法在第30,31行也创建了一个map变量的副本,存在本地变量表slot3中,但是要注意引用类型变量在本地变量表中存放的堆中对象的地址,而基本类型变量在本地变量表中存放的是具体值,所以在getMap()中finally代码中map.put("KEY","FINALLY")是对相同地址的堆中对象做了put操作,将“KEY”的值由“try”改为了“FINALLY”(HashMap不允许有同名key),但是之后将map=null的赋值操作43,44是将值赋值到slot1中,slot1中map的引用地址变成了null的引用地址,最后return返回(45,46)的是slot3中map的引用地址,所以调用getMap()方法返回的不是null,而是map对象(“KEY”,“FINALLY”)。
结论:try中有return,会在return之前执行finally中的代码,但是会保存一个副本变量,finally修改原来的变量,但try中return返回的是副本变量,所以如果是赋值操作,即使执行了finally中的代码,变量也不一定会改变,需要看变量是基本类型还是引用类型。这和方法传值还是传引用一样。