當 return 遇到 try

.java

.測試

.spa

.code

.blog

今天有同事和我探討在羣裏看到的一道有趣的題目,在探討的過程當中讓我搞清楚了一些曾經模糊的概念,特此記錄下來。內存

題目給出以下代碼,問運行後打印的結果是什麼。get

 1 public static void main(String []args) {
 2       System.out.println(fun());
 3 }
 4 public static int fun () {
 5       int x = 0;
 6       try {
 7           x = 1;
 8       } finally {
 9           ++x;
10       }
11       try {
12           return x;
13       } finally {
14           ++x;
15       }
16 }

嘗試運行,結果以下:(輸出 2)io

1 >$ javac -g No1.java
2 >$ javac No1
3 2
4 >$

爲什麼輸出是 2 而不是 3 呢,這個可能讓不少小夥伴有所疑惑,咱們經過 javap 指令查看字節碼來解釋這個疑問。table

首先來看一些前置知識——java 字節碼指令的 iconst_n、iload_n、istore_n 和 iinc。ast

指令 意義
iconst_n 表示將整型常量 n 推入棧頂,n 的範圍是 -1 ≤ n ≤ 5。
iload_n

表示將局部變量表中第 n 個槽的整型變量加載到操做數棧頂。

istore_n

表示將操做數棧頂的整型值彈出,並存儲到局部變量表的第 n 個槽中。

iinc a b 表示將局部變量表第 a 個槽中的值增長 b,並將結果存回 a 槽。

好,有了上面四個指令的基礎就夠了,接下來咱們看一下上面代碼的字節碼。

>$ javap -c -l No1
  public static int fun();
    Code:
       0: iconst_0                      // 常量 0 入棧頂
       1: istore_0                      // 將棧頂的 0 彈出並存入局部變量表的第 0 個槽中
       2: iconst_1                      // 常量 1 入棧頂
       3: istore_0                      // 彈出棧頂的 1 並存入局部變量表的第 0 個槽中,覆蓋原值
       4: iinc          0, 1            // 將局部變量表第 0 個槽中的值加 1,並將結果存回局部變量表第 0 個槽中,覆蓋原值
       7: goto          16              // 跳轉到 16
      10: astore_1
      11: iinc          0, 1
      14: aload_1
      15: athrow                        // 下面兩行是重點,將局部變量表第 0 個槽中的值拷貝了一個副本到局部變量表第 1 個槽中
      16: iload_0                       // 將局部變量表第 0 個槽中的值加載到棧頂
      17: istore_1                      // 彈出棧頂的值並存儲到局部變量表第 1 個槽中
      18: iinc          0, 1            // 將局部變量表第 0 個槽中的值加 1,並將結果存回局部變量表第 0 個槽中,覆蓋原值
      21: iload_1                       // 重點:將局部變量表中第 1 個槽中的值加載到棧頂(並無加載第 0 個槽中的值)
      22: ireturn                       // 返回棧頂的值
      23: astore_2
      24: iinc          0, 1
      27: aload_2
      28: athrow
    Exception table:
       from    to  target type
           2     4    10   any
          16    18    23   any

上面的文字太抽象,能夠對照着 圖1 的內容來理解。

圖1 執行過程內存圖例

在 圖1 中,紅色的字表示字節碼的行號,黑色的字表示執行此行字節碼以後,對應的內存中的值的變化。

不難看出,在字節碼第 1六、17 行,將 x 的值從局部變量表的第 0 個槽中拷貝了一個副本,保存在局部變量表的第 1 個槽中,而最後執行 ireturn 指令以前,將此副本加載到了棧頂,所以返回的值是在 finally 運算以前就肯定下來了的,此後 finally 中再次對 x 的運算都只是在局部變量表的第 0 個槽中作的,因此並不會影響到 ireturn 指令返回的值。

 

總結:

通過 LZ 幾番測試發現,不管在 try 中 return 的變量是否參與了後面在 finally 中的計算,都會被拷貝一個副本出來。

而 return 沒有在 try 塊中時,被 return 的變量則不會被拷貝副本。

因而可知,當 try 遇到 return 時,變量被「特殊照顧」了一下。

*注意,以上測試僅使用了 int 類型(基本數據類型),沒有測試 return 引用類型的狀況。

相關文章
相關標籤/搜索