天天看點

位元組碼分析finally塊對return傳回值的影響

直接進入主題。看如下代碼:

public int test(){
    int i=0;
    try {
        i=1;
        return i;
    } catch (Exception e) {
        i=2;
        return i;
    }finally{
        i=3;
    }
}           

複制

相信有點經驗的程式員一眼就能說出傳回的結果為1,但是您真的知道傳回的結果為什麼為1嗎?下面我們通過分析下目前方法的位元組碼,來說明為什麼。

檢視位元組碼指令:javap -verbose class檔案

知識點簡單概要:

看如下位元組碼需要簡單了解下棧的結構。

棧包括:局部變量表、操作數棧、動态連接配接、方法出口等。

下面位元組碼主要是對操作棧和局變量表的操作。

test方法的位元組碼如下:

stack=1, locals=5, args_size=1
    0: iconst_0                 将常量0壓入到操作棧頂
    1: istore_1                  将棧頂元素0存儲到局部變量表中的第二個slot中  (slot2=0)  i=0
    2: iconst_1                 将常量1壓入到操作棧頂
    3: istore_1                  将棧頂元素1存儲到局部變量表中的第二個slot中  (slot2=1)  i=1
    4: iload_1                   将局部變量中第二個變量 (i=1) 壓入到操作棧頂
    5: istore        4            将棧頂元素1存儲到局部變量表中的第四個slot中  (slot4=1) 
    7: iconst_3                 将常量3壓入到操作棧頂
    8: istore_1                  将棧頂元素3存儲到局部變量表中的第二個slot中  (slot2=1)  i=3
    9: iload         4            将局部變量中第四個變量 (slot4=1) 壓入到操作棧頂
   11: ireturn                   傳回操作棧頂值,這時操作棧中棧頂值為1。
   12: astore_2               将棧頂元素(e)存儲到局部變量表的第三個slot中
   13: iconst_2                将常量2壓入到操作棧頂
   14: istore_1                 将棧頂元素2存儲到局部變量表中的第二個slot中  (slot2=2)  i=2
   15: iload_1                  将局部變量中第二個變量 (i=2) 壓入到操作棧頂
   16: istore        4           将棧頂元素2存儲到局部變量表中的第四個slot中  (slot4=2) 
   18: iconst_3                将常量3壓入到操作棧頂
   19: istore_1                 将棧頂元素3存儲到局部變量表中的第二個slot中  (slot2=1)  i=3
   20: iload         4           将局部變量中第四個變量 (slot4=2) 壓入到操作棧頂
   22: ireturn                   傳回操作棧頂值,這時操作棧中棧頂值為2。
   23: astore_3               将棧頂元素(其它異常,Exception之外的)存儲到局部變量表的第四個slot中
   24: iconst_3                将常量3壓入到操作棧頂
   25: istore_1                将棧頂元素3存儲到局部變量表中的第二個slot中  (slot2=3)  i=3
   26: aload_3                将局部變量中第四個變量 (其它異常) 壓入到操作棧頂
   27: athrow                  抛出棧頂元素(異常資訊) 無傳回值           

複制

stack=1, locals=5, args_size=1
  • stack=1:操作棧的深度
  • locals=5:局部變量表中5個slot(槽位),每個slot存儲能一個變量(long、double 需要兩個slot存儲)
  1. this變量
  2. i 變量
  3. e 變量(Exception)
  4. Exception之外異常的變量
  5. 臨時存儲變量(傳回值從臨時存儲中傳回的)
  • args_size=1: 方法的參數個數(該方法無參數,為什麼這裡args_size為1呢?因為這個是執行個體方法,不是靜态方法,他預設會傳過來目前執行個體的引用,也就是this變量)

位元組碼執行路徑

通過位元組碼我們發現在編譯成class檔案的時候,已經把三種執行路徑都寫到class檔案中了。

  1. 第一種路徑

    第【1-11】行,程式正常執行順序(無異常)

  2. 第二種路徑

    第【12-22】行,程式報Exception的異常

  3. 第三種路徑

    第【23-27】行,程式報Exception之外的異常

位元組碼大白話解釋說明

第[0-1]行,代碼:int i=0;
第[2-3]行,try塊中代碼:i=1;
第[4-5]行,遇到return時,把 i 的值臨時存儲起來,然後執行finally中的代碼。
第[7-8] 行,finally塊代碼:i=3
第[9-11] 行,執行return語句,把臨時存儲的 i 值傳回。(執行finally代碼對傳回值無影響)
第[12]行,catch塊代碼: (Exception e)
第[13-14]行,catch塊代碼:  i=2
第[15-16]行,遇到catch塊中的return時,把 i 的值臨時存儲起來,然後執行finally中的代碼。
第[18-19]行,finally塊代碼:i=3
第[20-22]行,執行catch塊中return語句,把臨時存儲的 i 值傳回。(執行finally代碼對傳回值無影響)
第[23]行,局部變量表中存儲Exception之外的異常
第[24-25]行,finally塊代碼:i=3
第[26-27]行,将Exception之外的異常壓入棧頂,并抛出(無傳回值)           

複制

結論

通過位元組碼,我們發現,在try語句的return塊中,return 傳回的變量并不是直接傳回 i 值,而是在執行finally塊之前把i值存儲在臨時區域,當執行return時直接傳回的臨時區域中的值,即使在finally語句中把變量 i 的值修改了,也不會影響傳回的值。