您的位置:首頁>正文

java中return與finally的執行順序分析

大家應該都知道, 在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變數本身與返回值存放在不同的位置,

所以後期改了res後, returnValue未改變,

同類文章
Next Article
喜欢就按个赞吧!!!
点击关闭提示