大家應該都知道, 在java中無論是否出異常, finally中的代碼都會被執行的, 所以我們經常在裡面做些釋放連接的工作。 但如果有返回值, return與finally是怎麼樣執行的呢?首先看下面代碼。
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 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變數本身與返回值存放在不同的位置,