字節碼層面理解java中i++和++i的區別

還是讓我們從一道面試題說起吧,代碼如下,你知道方法執行最後會輸出什麼嗎?

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);

 

 

 

 

 

 

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