try,catch,finally字节码分析

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中的代码,变量也不一定会改变,需要看变量是基本类型还是引用类型。这和方法传值还是传引用一样。





评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值