在講 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的副本。