還是讓我們從一道面試題說起吧,代碼如下,你知道方法執行最後會輸出什麼嗎?
public static void main(String[] args) {
int i = 0;
for (int j = 0; j < 50; j++) {
i = i++;
}
System.out.println(i);
}
不賣關子,最後輸出結果是0,而不是50,不知道跟你的認知是否一致。
直接上字節碼,讓我們從字節碼層面看看i++背後的邏輯。
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=3, args_size=1
0: iconst_0
1: istore_1
2: iconst_0
3: istore_2
4: iload_2
5: bipush 50
7: if_icmpge 21
10: iload_1
11: iinc 1, 1
14: istore_1
15: iinc 2, 1
18: goto 4
21: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
24: iload_1
25: invokevirtual #3 // Method java/io/PrintStream.println:(I)V
28: return
先拋出這個方法局部變量表
0 | 1 | 2 |
args | i | j |
pc 0~1兩個指令,爲下標爲1的局部變量賦值爲0,也就是局部變量i
pc 2~3兩個指令,爲下表爲2的局部變量賦值爲0,也就是局部變量j
pc 4~7三個指令,取局部變量j的值與50比較,如果j>=50,跳轉到pc=21的指令處,如果不滿足則順序往下執行
pc 10~14三個指令,是i=i++這行代碼編譯後的指令。在jvm中,局部變量表和操作數棧是兩個不同的存儲數據的內存區域。iload_1表示將局部變量表中下標爲1的變量,也就是變量i的值複製一份,加載到操作數棧頂,innc 1,1 指令則將局部變量表中變量i的值加1再寫回局部變量表中變量i的位置,istore_1則將棧頂的數據覆蓋局部變量表中變量i的位置,所以執行完這3個命令後,變量i的值並沒有發生變化。用僞代碼來表示這三個指令的邏輯就是這樣
int stack_top = local_variable[1];//把下標爲1的局部變量加載到棧頂
local_variable[1] = local_variable[1] + 1;//下標爲1的局部變量自增1
local_variable[1] = stack_top;//用棧頂的值覆蓋下標爲1的局部變量
pc 15指令iinc 2,1 將變量j自增1
pc 18指令goto 4,程序重新從pc=4的地方開始執行
pc 21~25三個指令,就是打印下標爲1的局部變量,也就是打印變量i
所以,從pc10~14三個指令,可以看出變量i=i++這行代碼不會改變變量i的值,因此最後打印結果是0。
如果將 i=i++ 改成 i=++i,結果會是怎樣呢?
public static void main(String[] args) {
int i = 0;
for (int j = 0; j < 50; j++) {
i = ++i;
}
System.out.println(i);
}
輸出結果是50,還是直接看字節碼
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=3, args_size=1
0: iconst_0
1: istore_1
2: iconst_0
3: istore_2
4: iload_2
5: bipush 50
7: if_icmpge 21
10: iinc 1, 1
13: iload_1
14: istore_1
15: iinc 2, 1
18: goto 4
21: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
24: iload_1
25: invokevirtual #3 // Method java/io/PrintStream.println:(I)V
28: return
除了指令10~14,對應的代碼就是 i=++i,其他部分跟上面的字節碼指令一樣,所以我們只看不一樣的部分。
pc 10 innc 1,1 這裏先執行自增指令,將下標爲1的局部變量i的值自增1
pc 13 iload_1 將下標爲1的局部變量i的值加載到操作數棧頂
pc 14istore_1 將操作數棧頂的值覆蓋下標爲1的局部變量i的值
用僞代碼來表示這段邏輯就是這樣
local_variable[1] = local_variable[1] + 1;//下標爲1的局部變量自增1
int stack_top = local_variable[1];//把下標爲1的局部變量加載到棧頂
local_variable[1] = stack_top;//用棧頂的值覆蓋下標爲1的局部變量
所以,從pc10~14三個指令,可以看出變量 i=++i 這行代碼會使i的值增加1,因此最後打印結果是50。
與i++對應的指令不同的地方是,++i會先執行innc 1,1指令,這條指令會是i的值增加1,然後再參與計算。而i++會先將i的值保存到另外一個地方,然後再對i自增1,但是i=i++的賦值(也就是=)會用已保存的i的舊值覆蓋i的新值,所以i=i++,i的值並不會變。
總結:講解了i=i++和i=++i的字節碼底層原理。
不知道朋友你理解了沒有,試試這道題輸出結果是什麼
int i = 0;
int result = i++ + ++i + i++;
System.out.println(result);