Java進階--Java中 i++徹底解析

在講 i++與++i以前先看兩個在筆試面試中常常遇到的題目: java


題目1:面試


//代碼1
        int x=2; 
        int b=(x++)*3;
        System.out.println(b);

/*
 輸出結果爲:6
*/
複製代碼


題目2:bash


//代碼1
       int i=0;
       for(int j=0;j<100;j++)          
           i=i++;   
       System.out.println(i);
/*
 輸出結果爲:0
*/
複製代碼


題目3:jvm


public  int inc()
   {
       int x;
       try {
        x=1;
        return x;
    } catch (Exception e) {
        x=2;
        return x;
    }
    finally{
        x=3;
       }       
   }
複製代碼


上面的輸出結果在Java中是正確的,跟Java自身的處理機制有關,Java虛擬機執行字節碼是基於棧的體系結構。ui


下面就來具體分析一下爲何會有如此奇怪的結果,在Java中變量在運算的時候涉及到兩個區域這兩個區域分別爲 stack 區域和 local variable 區域,stack 在變量進行運算時會用到,而local variable區域用來保存局部變量的值。spa


先看看題目1的字節碼:3d


L0
    LINENUMBER 7 L0
    ICONST_2
    ISTORE 1
   L1
    LINENUMBER 8 L1
    ILOAD 1
    IINC 1 1
    ICONST_3
    IMUL
    ISTORE 2
   L2
    LINENUMBER 9 L2
    GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
    ILOAD 2
    INVOKEVIRTUAL java/io/PrintStream.println (I)V
   L3
    LINENUMBER 14 L3
    RETURN
複製代碼


先對上面的指令進行一個講解:指針


① ICONST_2:將2壓入到stack中。code


②ISTORE 1:將stack中的值取出存放到 local variable 區域的位置1處。orm


③ILOAD 1:local variable 區域的位置1處的值壓入到stack中。


④IINC 1 1:將local variable 區域的位置1處的值加1。


⑤ICONST_3:將3壓入到stack中。


⑥IMUL:彈出stack中的兩個元素,相乘將結果壓入到stack中。


⑦ISTORE 2:將stack中的結果取出存放到local variable區域。


下面是題目1運行時stack和local variable區域的值的狀況:


① ICONST_2 :將2壓入到stack中。

②ISTORE 1:將stack中的值取出存放到 local variable 區域的位置1處。

③ILOAD 1:local variable 區域的位置1處的值壓入到stack中。

④IINC 1 1:將local variable 區域的位置1處的值加1。

⑤ICONST_3:將3壓入到stack中。

⑥IMUL:彈出stack中的兩個元素,相乘將結果壓入到stack中。

⑦ISTORE 2:將stack中的結果取出存放到local variable區域。

最後輸出的是local variable中位置2的值,上面計算結果比較好理解關鍵在於④IINC 1 1這一步直接將local variable中位置1的值加1並非放到stack中加1,stack中的2後面一直未改變。也就是說stack至關於一箇中轉站。


題目1清楚後題目2也是比較好理解的,下面直接上題目2的字節碼,對照着字節碼看更清楚整個流程。


L0
    LINENUMBER 7 L0
    ICONST_0
    ISTORE 1
L1
    LINENUMBER 8 L1
    ICONST_0
    ISTORE 2
L2
    GOTO L3
L4
    LINENUMBER 9 L4
   FRAME APPEND [I I]
    ILOAD 1
    IINC 1 1
    ISTORE 1
L5
    LINENUMBER 8 L5
    IINC 2 1
L3
   FRAME SAME
    ILOAD 2
    BIPUSH 100
    IF_ICMPLT L4
L6
    LINENUMBER 10 L6
    GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
    ILOAD 1
    INVOKEVIRTUAL java/io/PrintStream.println (I)V
L7
    LINENUMBER 11 L7
    RETURN
//-----------------------------------
字節碼
public int inc();
Code:
Stack=1,Locals=5,Args_size=1
0:iconst_1//try塊中的x=1
1:istore_1
2:iload_1//保存x到returnValue中,此時x=1
3:istore 4
5:iconst_3//finaly塊中的x=3
6:istore_1
7:iload 4//將returnValue中的值放到棧頂,準備給ireturn返回
9:ireturn
10:astore_2//給catch中定義的Exception e賦值,存儲在Slot 2中
11:iconst_2//catch塊中的x=2
12:istore_1
13:iload_1//保存x到returnValue中,此時x=2
14:istore 4
16:iconst_3//finaly塊中的x=3
17:istore_1
18:iload 4//將returnValue中的值放到棧頂,準備給ireturn返回
20:ireturn
21:astore_3//若是出現了不屬於java.lang.Exception及其子類的異常纔會走到這裏
22:iconst_3//finaly塊中的x=3
23:istore_1
24:aload_3//將異常放置到棧頂,並拋出
25:athrow
Exception table:
from to target type
0 5 10 Class java/lang/Exception
0 5 21 any
10 16 21 any
複製代碼


題目3中涉及到異常,可是對題目3的分析可以讓咱們更加理解jvm的運行機制。這段代碼的返回值應該是多少?


對Java語言熟悉的讀者應該很容易說出答案:若是沒有出現異常,返回值是1;若是出現了Exception異常,返回值是2;若是出現了Exception之外的異常,方法非正常退出,沒有返回值。


分析一下題目3字節碼的執行過程,從字節碼的層面上看看爲什麼會有這樣的返回結果。


字節碼中第0~4行所作的操做就是將整數1賦值給變量x,而且將此時x的值複製一份副本到最後一個本地變量表的returnValue中若是這時沒有出現異常,則會繼續走到第5~9行,將變量x賦值爲3,而後將以前保存在returnValue中的整數1讀入到操做棧頂,最後ireturn指令會以int形式返回操做棧頂中的值,方法結束。


若是出現了異常,PC寄存器指針轉到第10行,第10~20行所作的事情是將2賦值給變量x,而後將變量x此時的值賦給returnValue,最後再將變量x的值改成3。


方法返回前一樣將returnValue中保留的整數2讀到了操做棧頂。


從第21行開始的代碼,做用是變量x的值賦爲3,並將棧頂的異常拋出,方法結束。


注意:理解整個過程須要注意的一點是return x不是直接返回x的值返回的是x的一個副本,遇到return x時就local variable中保存一個x的副本,而後再返回 x的副本。

相關文章
相關標籤/搜索