記得之前上學的時候,老師講過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 , 這個語句是原子操作嗎?在多線程下的表現如何?