1. 程式人生 > >i=i++輸出結果的原因

i=i++輸出結果的原因

package algorithms.com.guan.javajicu; 

public class Inc { 

    public static void main(String[] args) { 

       Inc inc = new Inc(); 

       int i = 0; 

       

inc.fermin(i); 

       i= i ++; 

       System.out.println(i);

    

    

    void fermin(int i){ 

       

i++; 

    

}

輸出的i值是多少?

淺顯版:

Java使用了中間快取變數機制:
i=i++;等同於:
temp=i; (等號右邊的i)
i=i+1;      (等號右邊的i)
i=temp;   (等號左邊的i)
而i=++i;則等同於:
i=i+1;
temp=i;
i=temp;

 

深層次版:

我們單獨看問題中的這兩句程式碼。

1

int i = 0; i = i++;

Java虛擬機器棧(JVM Stack)描述的是Java方法執行的記憶體模型,而JVM記憶體模型是基於“棧幀”的,每個棧幀中都有 區域性變量表 和 運算元棧 (還有動態連結、return address等),那麼JVM是如何執行這個語句的呢?通過javap大致可以將上面的兩行程式碼翻譯成如下的JVM指令執行程式碼。

0: iconst_0

1: istore_1

2: iload_1

3: iinc          1, 1

6: istore_1

7: iload_1

接下來分析一下JVM是如何執行的:

第0:將int型別的0入棧,就是放到運算元棧的棧頂

第1:將運算元棧棧頂的值0彈出,儲存到區域性變量表 index (索引)值為1的位置。(區域性變量表也是從0開始的,0位置一般儲存當前例項的this引用,當然靜態方法例外,因為靜態方法是類方法而不是例項方法)

第2:將區域性變量表index 1位置的值的副本入棧。(這時區域性變量表index為1的值是0,運算元棧頂的值也是0)

第3:iinc是對int型別的值進行自增操作,後面第一個數值1表示,區域性變量表的index值,說明要對此值執行iinc操作,第二個數值1表示要增加的數值。(這時區域性變量表index為1的值因為執行了自增操作變為1了,但是運算元棧中棧頂的值仍然是0)

第6:將運算元棧頂的值彈出(值0),放到區域性變量表index為1的位置(舊值:1,新值:0),覆蓋了上一步區域性變量表的計算結果。

第7:將區域性變量表index 1位置的值的副本入棧。(這時區域性變量表index為1的值是0,運算元棧頂的值也是0)

 

總結:從執行順序可以看到,這裡第1和第6執行了2次將0賦值給變數i的操作(=號賦值),i++操作是在這兩次操作之間執行的,自增操作是對區域性變量表中的值進行自增,而棧頂的值沒有發生變化,這裡需要注意的是儲存這個初始值的地方是運算元棧而不是區域性變量表,最後再將棧頂的值覆蓋到區域性變量表i所在的索引位置中去。

 

PS.

方法傳遞引數不改變值的解釋:

關於第二個陷阱(為什麼 fermin方法沒有影響到i的值 )的解答看下面。

1

inc.fermin(i);

1. java方法之間的引數傳遞是 值傳遞 而不是 引用傳遞

2. 每個方法都會有一個棧幀,棧幀是方法執行時的資料結構。這就是說每個方法都有自己獨享的區域性變量表。(更嚴謹的說法其實是每個執行緒在執行每個方法時都有自己的棧幀,或者叫當前棧幀 current stack frame)

3. 被呼叫方法fermin()的形式引數int i 實際上是呼叫方法main()的實際引數 i 的一個副本。

4. 方法之間的引數傳遞是通過區域性變量表實現的,main()方法呼叫fermin()方法時,傳遞了2個引數:

第0個隱式引數是當前例項(Inc inc = new Inc(); 就是inc引用的副本,引用/reference 是指向物件的一個地址,32位系統這個地址佔用4個位元組,也就是用一個Slot來儲存物件reference,這裡傳遞的實際上是reference的一個副本而不是 reference本身 );

第1個顯示引數是 i 的一個副本。所以 fermin()方法對 i 執行的操作只限定在其方法獨享或可見的區域性變量表這個範圍內,main()方法中區域性變量表中的i不受它的影響;

 

如果main()方法和fermin()方法共享區域性變量表的話,那答案的結果就會有所不同。 其實你自己思考一下,就會發現, JVM虛擬機器團隊這麼設計是有道理的。

會有什麼不同呢?若main()方法和fermin()共享區域性變量表,則會出現

       int i = 0; 

       inc.fermin(i); 

       i= i ++;  

等同於

       int i = 0; 

       i++;

       i= i ++;  

 

0: iconst_0

1: istore_1

2: iinc          1, 1

3: iload_1

4: istore_1

5: iload_1

6: iinc          1, 1

7: istore_1

6: iload_1