經過指令碼來判斷Java代碼的執行順序(++問題與return和finally的問題)

問題

在《深刻理解Java虛擬機》一書中遇到了以下代碼:java

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

因爲曾經搜了一下return和finally的問題後,只是簡單的看到了finally會執行,從而致使本身誤覺得只是簡單地把finally的執行順序放到return語句以前,所以判斷這段代碼的執行結果應該是3,可實際運行結果是1。研究後發現本身當初真是太糊塗,因而便記錄下來。工具

工具

咱們都知道,class文件中的內容就是可供JVM理解的字節碼,JVM也是根據class的字節碼來執行程序代碼,因此class文件中就包含着程序代碼最終的執行順序。code

咱們能夠經過官方提供的javap -c 再加上class文件的路徑來獲得各個方法對應的指令碼。get

例如:javap -c Test.class虛擬機

引例

因爲是打算使用JVM的指令碼來解決這個問題,剛開始先以一個簡單的方法來講明一下。對於以下方法:io

public int method1() {
    int i = 1;
    return i;
}

該方法對應的指令碼爲:table

public int method1();
    Code:
       0: iconst_1
       1: istore_1
       2: iload_1
       3: ireturn

每一個指令對應着一個操做,上面的指令碼意思是:ast

  1. 將int型數值0推送至棧頂
  2. 將棧頂int型元素存入第二個空間中
  3. 將第二個空間的int型元素推送至棧頂
  4. 返回將棧頂的int型元素並退出這個方法

由此能夠看出,經過指令碼,咱們能夠直觀地看到程序代碼的執行順序,這對於解決任何執行順序的問題是一個利器。class

若是仍是感受有些不明因此,那咱們能夠再看看i++++i的問題。對於以下代碼:變量

// return 1
public int method2() {
    int i = 1;
    return i++;
}

// return 2
public int method3() {
    int i = 1;
    return ++i;
}

它們的指令碼分別是:

public int method2();
    Code:
       0: iconst_1
       1: istore_1
       2: iload_1
       3: iinc          1, 1
       6: ireturn

  public int method3();
    Code:
       0: iconst_1
       1: istore_1
       2: iinc          1, 1
       5: iload_1
       6: ireturn

顯然,這兩段指令碼最大的區別就是iinc 1,1指令的位置不一樣,並且若是把這條指令刪除,那麼與method1的指令碼徹底一致,對應源代碼來看,這條指令就是++這個符號的影響了。

而這個關鍵的iinc 1,1指令的做用哪怕徹底不懂也能猜出來,就是將第二個空間的int數據+1後再放回第二個空間

將這個含義放到指令碼中再從新捋一遍,以method2爲例:

  1. 將int型數值0推送至棧頂
  2. 將棧頂int型元素存入第二個空間中
  3. 將第二個空間的int型元素(1)推送至棧頂
  4. 將第二個空間的int數據+1後再放回第二個空間
  5. 返回將棧頂的int型元素並退出這個方法

須要注意的是,第三步是將1而不是整個空間推送至棧頂,因此第四步對第二個空間中的數據1加1後並無改變棧頂的值,所以返回值爲1。相對的,method2則是:

  1. 將int型數值0推送至棧頂
  2. 將棧頂int型元素存入第二個空間中
  3. 將第二個空間的int數據+1後再放回第二個空間
  4. 將第二個空間的int型元素(2)推送至棧頂
  5. 返回將棧頂的int型元素並退出這個方法

因此,返回的是2。

解決

如今咱們能夠看最初的method方法了,在這裏再複製一遍代碼:

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

對應的指令碼:

public int method();
    Code:
       0: iconst_1
       1: istore_1
       2: iload_1
       3: istore        4
       5: iconst_3
       6: istore_1
       7: iload         4
       9: ireturn
      10: astore_2
      11: iconst_2
      12: istore_1
      13: iload_1
      14: istore        4
      16: iconst_3
      17: istore_1
      18: iload         4
      20: ireturn
      21: astore_3
      22: iconst_3
      23: istore_1
      24: aload_3
      25: athrow
    Exception table:
       from    to  target type
           0     5    10   Class java/lang/Exception
           0     5    21   any
          10    16    21   any

這段指令碼不一樣的地方在於最後有一個異常表,咱們先不用管它,先看到第一個ireturn指令的指令碼,即代碼中的第9行爲止的指令碼:

0: iconst_1
1: istore_1
2: iload_1
3: istore        4
5: iconst_3
6: istore_1
7: iload         4
9: ireturn

這段指令碼就是當沒有異常時,程序執行的指令碼,finally語句塊的指令碼已經包含在裏面了:

  1. 將int型數值1推送至棧頂
  2. 將棧頂int型元素存入第二個空間中
  3. 將第二個空間的int型元素(1)推送至棧頂
  4. 將棧頂int型元素存入第五個空間中
  5. 將int型數值3推送至棧頂
  6. 將棧頂int型元素存入第二個空間中(3
  7. 第五個空間的int型元素(1)推送至棧頂
  8. 返回將棧頂的int型元素並退出這個方法

由此能夠看出,方法返回的是第五個空間的1而不是第二個空間的3,和運行結果一致。

其中,關鍵的地方就是第四步以及第七步。因而可知,Java程序在執行時遇到return語句時,會先將方法的返回值保存起來,若是還有finally語句塊,那麼就先執行finally語句塊,最後再將返回值取出後返回

另外,若是return後跟的是表達式或者方法,那麼會先計算出最終的返回值後再執行finally語句塊,可自行驗證。

固然,若是保存的返回值是一個引用類型的變量,那麼在finally代碼塊中修改則會改變這個變量自己的屬性,於是改變返回值的屬性,畢竟finally的代碼是的的確確執行過了。

例如,返回一個List,在finally中又對List進行了增長或刪除,那麼返回的List的內容天然也變了。

附加

關於指令碼其他的部分,涉及到更多知識,在這裏根據個人理解簡單說一下。

這段指令碼最後有一個異常表,它的含義能夠簡單解釋爲:在[from,to)的區間內,若是發生type類型的異常,那麼就跳到target執行。

正由於有了異常表的存在,在出現異常時,程序能夠根據產生的異常來跳到正確的位置執行接下來的代碼。

[10,20]即爲catch代碼塊對應的指令碼,不過其中會把捕捉到的異常存儲下來,也就是源代碼中的Exception e。[21,25]則是會把try語句塊中拋出的catch沒有捕捉的異常保存下來,而後執行finally的代碼,最後拋出該異常結束方法。

這三片指令碼都包含了finally的指令碼,也就保證了源代碼中finally的代碼確定會執行。

結論

Java程序在執行時遇到return語句時,會先將方法的返回值保存起來,若是還有finally語句塊,那麼就先執行finally語句塊,最後再將返回值取出後返回。另外,若是return後跟的是表達式或者方法,那麼會先計算出最終的返回值後再執行finally語句塊。

筆記內容只是本人思考而寫,若是有什麼問題,還請指出,謝謝!

相關文章
相關標籤/搜索