關於i++與++i的一道題

前幾天刷LeetCode題,看到評論區有人問了一個有意思的問題,代碼如下:

public static void main(String[] args) {
	int a[] = {1, 2, 3, 4, 5};
	int num = 3;
	a[num - 1] = a[num-- - 1];
	for (int i : a) {
		System.out.println(i);
	}
}

問輸出結果應該是1、2、3、4、5,還是1、2、2、4、5?

那如果把num--變成--num,結果又是什麼?

一般上學認真聽講的可能都知道結果,num--的時候輸出1、2、3、4、5。

--num的時候,輸出1、2、2、4、5。

從上學剛學編程,老師就教過,i--是先取i的值,然後i再減1。--i是先減1,再取i的值。

本着刨根問底的精神,特別想知道這兩個操作虛擬機到底是怎麼執行的。我們javap -c看一下字節碼是什麼樣的。

先看--num時候:

Compiled from "Test.java"
public class Test {
  public Test();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public static void main(java.lang.String[]);
    Code:
       0: iconst_5                  
       1: newarray       int        
       3: dup                     
       4: iconst_0                 
       5: iconst_1                  
       6: iastore                
       7: dup                       
       8: iconst_1
       9: iconst_2
      10: iastore
      11: dup
      12: iconst_2
      13: iconst_3
      14: iastore
      15: dup
      16: iconst_3
      17: iconst_4
      18: iastore
      19: dup
      20: iconst_4
      21: iconst_5
      22: iastore
      23: astore_1
      24: iconst_3
      25: istore_2
      26: aload_1
      27: iload_2
      28: iconst_1
      29: isub
      30: aload_1
      31: iinc          2, -1
      34: iload_2
      35: iconst_1
      36: isub
      37: iaload
      38: iastore
      39: aload_1
      40: astore_3
      41: aload_3
      42: arraylength
      43: istore        4
      45: iconst_0
      46: istore        5
      48: iload         5
      50: iload         4
      52: if_icmpge     75
      55: aload_3
      56: iload         5
      58: iaload
      59: istore        6
      61: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
      64: iload         6
      66: invokevirtual #3                  // Method java/io/PrintStream.println:(I)V
      69: iinc          5, 1
      72: goto          48
      75: return
}

我們從頭開始逐行分析:

0:iconst_5:表示常量5入棧

1:newarray int:分配int類型數組,需要彈出棧頂數據作爲數組長度

3:dup:複製棧頂內容,這裏就是複製了一份數組

接下來4到22行,都是數據賦值,我們只看一組4到7行:

4:iconst_0:常量0入棧

5:iconst_1:常量1入棧

6:iastore:將棧頂int數據存入指定數組的指定索引位置,需要出棧3次,所以每次賦值後都要dup複製棧頂元素。

數據初始化完,從23行繼續看:

23:astore_1:將棧頂元素存入第二個本地變量,在java方法中局部變量數組中按順序要放入:this(static方法沒有)、參數、局部變量。所以這裏第二個變量就是我們聲明的數組a。

此時棧分佈是空的:

 
 

 

接下來24、25行:

24:iconst_3:常量3入棧

3
 

 

25:istore_2:將棧頂元素保存到第三個本地變量,就是給我們聲明的num賦值3

 
 

 

 

接下來就是取值了:

26:aload_1:將第二個引用類型入棧,這裏就是數組a入棧

27:iload_2:將第三個整數類型入棧,這裏就是num入棧

28:iconst_1:常量1入棧

1
num=3
a
 

 

 

29:isub:棧頂兩個元素減法,結果入棧。就是num-1結果入棧

2
a

 

30:aload_1:將第二個引用類型入棧,這裏就是數組a入棧

a

2

a

 

31:iinc 2,-1:將第三個本地變量與-1相加,此時num變爲2

34:iload_2:將第三個整數類型入棧,這裏就是num入棧

35:iconst_1:常量1入棧

1
num=2
a
2
a

 

36:isub:棧頂兩個元素減法,結果入棧,即num-1

1
a
2
a

 

37:iaload:int類型數組棧頂位置索引的數值入棧,這裏就是a[num-1]入棧

a[1]=2
2
a

 

38:iastore:將棧頂int類型數據存儲到int類型數組指定索引位置。

這裏也就是a[2]=a[1],所以打印結果是1、2、2、4、5。

 

我們再看下num--情況下的字節碼:

Compiled from "Test.java"
public class Test {
  public Test();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public static void main(java.lang.String[]);
    Code:
       0: iconst_5
       1: newarray       int
       3: dup
       4: iconst_0
       5: iconst_1
       6: iastore
       7: dup
       8: iconst_1
       9: iconst_2
      10: iastore
      11: dup
      12: iconst_2
      13: iconst_3
      14: iastore
      15: dup
      16: iconst_3
      17: iconst_4
      18: iastore
      19: dup
      20: iconst_4
      21: iconst_5
      22: iastore
      23: astore_1
      24: iconst_3
      25: istore_2
      26: aload_1
      27: iload_2
      28: iconst_1
      29: isub
      30: aload_1
      31: iload_2
      32: iinc          2, -1
      35: iconst_1
      36: isub
      37: iaload
      38: iastore
      39: aload_1
      40: astore_3
      41: aload_3
      42: arraylength
      43: istore        4
      45: iconst_0
      46: istore        5
      48: iload         5
      50: iload         4
      52: if_icmpge     75
      55: aload_3
      56: iload         5
      58: iaload
      59: istore        6
      61: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
      64: iload         6
      66: invokevirtual #3                  // Method java/io/PrintStream.println:(I)V
      69: iinc          5, 1
      72: goto          48
      75: return
}

唯一的區別就在於31行,在num--的情況下,執行iinc 2,-1前,先執行了iload_2,即對局部變量修改前,提前把局部變量值入棧。

至此,分析完畢。

通過分析字節碼追溯該問題的根源,還是很有意思的。

字節碼其實在編碼中還是會常用到的,比如虛擬機是怎麼做try...catch的等等,從字節碼上可以很清楚的理解。

 

 

 

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