關於i++和++i (java)

記得之前上學的時候,老師講過i++和++i的區別是:i++後執行加法,++i是先執行加法。但是之前有一個疑問,如果這個命令放到一個表達式裏,所謂的i++的後執行是在表達式最後執行嗎?面試的時候也總會遇到一個問題:i=i++最後的結果是什麼?

所謂源碼之前,了無祕密。我們從彙編看一下就知道到底答案是什麼了。

試驗1:i=i++

試驗2:i=++i

試驗3:i=i++ + i++

public class Test {
    public static void main(String[] args) {
        int i = 1;
        i = i++;
        System.out.println(i);
        i = ++i;
        System.out.println(i);
        i = i++ + i++;
        System.out.println(i);
    }
}

輸出結果如下:

1
2
5

好了,我們看一下彙編,就知道爲什麼了:javap -v Test.class

省略上面的常量池信息,直接看函數信息

下面對於i的操作都是在虛擬機棧中進行的。函數在編譯後會生成棧幀,每一個函數從調用開始到執行完成的過程就對應着一個棧幀在虛擬機棧裏面從入棧到出棧的過程。i就存在其中的局部變量表裏,iload是把變量i加載到操作數棧中(下面的所有棧也都是指操作數棧)。

 public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=2, args_size=1
         0: iconst_1     將常量1放到棧頂
         1: istore_1     把棧頂值出棧,存到1號變量,也就是i
         2: iload_1      加載1號變量加載到棧頂(這裏注意一下)
         3: iinc          1, 1    這裏的兩個1不一樣,第一個1代表1號變量,第二個1代表常量1,這是給1號變量加1的操作,直接修改局部變量表(此時變量i的值爲2)
         6: istore_1      把棧頂值出棧,存到1號變量,也就是賦值操作(此時變量i的值爲1)
         7: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;   
        10: iload_1
        11: invokevirtual #3                  // Method java/io/PrintStream.println:(I)V
        14: iinc          1, 1     先對1號變量進行加1操作 (此時i=2)
        17: iload_1                將1號變量加載到棧頂
        18: istore_1               將棧頂值出棧,存到1號變量,也就是賦值操作(i=2)
        19: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
        22: iload_1
        23: invokevirtual #3                  // Method java/io/PrintStream.println:(I)V
        26: iload_1                將1號變量加載到棧頂
        27: iinc          1, 1     對1號變量加1(i=3)
        30: iload_1                將1號變量加載到棧頂
        31: iinc          1, 1     對1號變量加1(i=4)
        34: iadd                   將棧頂兩個數字出棧,相加(2+3),並將結果放在棧頂
        35: istore_1               將棧頂數字出棧,保存在1號變量(i=5)
        36: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
        39: iload_1
        40: invokevirtual #3                  // Method java/io/PrintStream.println:(I)V
        43: iinc          1, 1      對1號變量加1(i=6)
        46: iload_1                 將1號變量加載到棧頂
        47: iinc          1, 1      對1號變量加1(i=7)
        50: iload_1                 將1號變量加載到棧頂
        51: iadd                    將棧頂兩個數字出棧,相加(6+7),並將結果放在棧頂
        52: istore_1                將棧頂數字出棧,保存在1號變量(i=13)
        53: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
        56: iload_1
        57: invokevirtual #3                  // Method java/io/PrintStream.println:(I)V
        60: return
      LineNumberTable:
        line 5: 0
        line 6: 2
        line 7: 7
        line 8: 14
        line 9: 19
        line 10: 26
        line 11: 36
        line 12: 43
        line 13: 53
        line 14: 60
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      61     0  args   [Ljava/lang/String;
            2      59     1     i   I

補充試驗:

試驗4:i++

試驗5:++i

public class Test {
    public static void main(String[] args) {
        int i = 0;
        i++;
        ++i;
    }
}

彙編結果如下:

 Code:
      stack=1, locals=2, args_size=1
         0: iconst_0
         1: istore_1
         2: iinc          1, 1
         5: iinc          1, 1
         8: return

從上面的彙編,我們可以看出:

(1)單獨使用i++和++i的時候沒有什麼區別,都是 iinc 1,1。直接在局部變量表中對數據進行修改

(2)i++和++i在複製語句或者其他表達式中出現的時候,會有一個load操作,將i從局部變量表加載到操作數棧進行計算

而i++和++i確實是如老師所說的區別:一個後加1,一個先加1。這個先後的參照物也就是load操作。

 

本文章的實驗和結論都是在單線程情況下進行的。我們能看到i++和++i最終都只被編譯成了一條語句 iinc 1,1  , 這個語句是原子操作嗎?在多線程下的表現如何?

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章