大家应该都知道,在java中无论是否出异常,finally中的代码都会被执行的,所以我们经常在里面做些释放连接的工作。 但如果有返回值,return与finally是怎么样执行的呢?首先看下面代码。
?
class="java" name="code">public class App { public String getName(String name){ String res=""; try { res=name; return res; }finally { res="zhangsan"; } } public static void main(String[] args) throws InterruptedException { App app=new App(); String name=app.getName("wangwu"); System.out.println(name); } } //结果:wangwu
下面我们根据生成的字节码来分析下为什么出现这个结果。
?
首先进入这个类生成的App.class文件目录,执行命令:javap -c -verbose App
这样就打印出了这个类的字节码信息。下面贴一下我们本次分析需要的内容。
?
public java.lang.String getName(java.lang.String); descriptor: (Ljava/lang/String;)Ljava/lang/String; flags: ACC_PUBLIC Code: stack=1, locals=5, args_size=2 0: ldc #2 // String 2: astore_2 3: aload_1 4: astore_2 5: aload_2 6: astore_3 7: ldc #3 // String zhangsan 9: astore_2 10: aload_3 11: areturn 12: astore 4 14: ldc #3 // String zhangsan 16: astore_2 17: aload 4 19: athrow Exception table: from to target type 3 7 12 any 12 14 12 any LineNumberTable: line 16: 0 line 18: 3 line 19: 5 line 21: 7 line 19: 10 line 21: 12 LocalVariableTable: Start Length Slot Name Signature 0 20 0 this Lcom/qlteacher/App; 0 20 1 name Ljava/lang/String; 3 17 2 res Ljava/lang/String;
?首先解释下各个命令:
?
ldc:将int,float或者String类型常量从常量池推送至栈顶。
astore:将栈顶引用型类型数据存入指定本地变量。
aload:将制定的引用类型变量推送至栈顶
?
方法的简要执行:
在jvm中,每个线程都具有自己的虚拟机栈。当执行方法时,如上面的getName,就会创建一个栈帧(存储局部变量表,操作数栈等信息)进入虚拟机栈。每一个方法从调用到执行完毕,就是一个栈帧从虚拟机栈中入栈到出栈的过程。
?
下面分析下字节码:
?
首先看这行:
stack=1, locals=5, args_size=2,根据这行提示我们能知道这个方法栈的深度为1,局部变量表里有5个数值,参数大小为2.
但我们这个方法getName(String?monospace; line-height: 1.5; background-color: #fafafa;">name)只有一个方法啊,哪来的两个。因为对于实例方法,编译器会默认添加一个参数:this,代表对当前实例的引用。这就是我们能在代码中使用this. 的原因。
?
0: ldc ? ? ? ? ? #2?
2: astore_2
上面这两个个命令就是对应?String res="";
首先ldc命令,将常量池中对应 #2(常量池代码没有贴上来)也就是 "",放入操作数栈。
然后将操作数栈数据出栈,并且存入局部变量表的下标为2的slot中。(为什么存入第三个呢,因为第一个是上面说的this,第二个是方法的参数name)
?
3: aload_1
?
4: astore_2
上面这两个个命令就是对应??res=name;
首先aload_1是将局部变量表中的第二个数值(也就是我们的参数name)取出来放入操作数栈
然后astore_2 将刚才的数出栈并且存入局部变量表的第三个位置,也就是上面res的位置,这样就完成了将name的值赋给了res。
?
5: aload_2
?
6: astore_3
代码继续执行,不出异常的话就因该执行return res;这句代码了。
当执行到这句代码的时候,首先从局部变量表第三个位置(res变量)取出res的数值放入操作数栈顶。然后出栈放入操作数栈的第四个位置(为了方便,我们暂且给它起个名字为returnValue)。
通过上面命令这个方法需要返回的值就已经确定并存储好了。
?
7: ldc ? ? ? ? ? #3 ? ? ? ? ? ? ? ? ?// String zhangsan
?
9: astore_2
这两句就是finally中的语句了。
首先将常量池对应 #3的常量(zhangsan)压入操作数栈。然后将这个数出栈astore_2并且存入局部变量表的第三个位置(res),这样就完成了res="zhangsanj";需要注意的是此时虽然改了res,但我们上面所存放的returnValue值还未改变。
?
10: aload_3
?
11: areturn
这两步就是return操作了,将局部变量表中第四个位置的数值(上面的returnValue)压入操作数栈,然后返回。
?
到这里,这个方法就执行完了。后面的代码是异常分支,当出现异常的时候会走下面的代码,这里不再分析。
?
总结:
当执行代码碰到return的时候,会将要返回的值存入局部变量表(暂且起名为returnValue)。如果有finally代码块,就会执行finally中的代码,执行完毕后,取出returnValue中的内容,进行反回。
?
上面代码中res作为返回值,但res变量本身与返回值存放在不同的位置,所以后期改了res后,returnValue未改变,
?
?
?
?
?
?
?