看字節碼之前需要先了解相關概念,如棧幀、操作數棧、局部變量表。
棧幀是JVM中很重要的一個概念,因爲JVM是基於棧的架構。一個方法的調用其實就是棧幀入棧出棧的過程。棧頂棧幀就是當前方法調用。
一個棧幀中包含:
- 局部變量表
- 操作數棧
- 動態鏈接
- 方法返回地址
這裏i++、 ++i涉及到的就是局部變量表和操作數棧。具體信息可參考:《Java虛擬機規範》
局部變量表存儲的是方法的參數以及內部定義的變量的值,操作數棧也是一個棧結構,用來執行方法中的指令。
好了,來看一個代碼片段:
class Scratch {
public static void main(String[] args) {
int i=0,j=0,m=0;
j = i++;
m = ++i;
}
}
爲了不產生其他多過信息,這裏只寫了關鍵代碼。
首先可以通過javac
將源碼編譯成class:
javac scratch.java
執行完成後將看到Scratch.class 文件,通過javap命令查看字節碼:
javap -c Scratch > scratch.txt
爲了方便查看將結果輸出到了scratch.txt,打開此文件將看到如下信息:
Compiled from "scratch.java"
class Scratch {
Scratch();
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_0
1: istore_1
2: iconst_0
3: istore_2
4: iconst_0
5: istore_3
6: iload_1
7: iinc 1, 1
10: istore_2
11: iinc 1, 1
14: iload_1
15: istore_3
16: return
}
關注main方法 0-5,可以發現對應的是:
int i=0,j=0,m=0;
主要是聲明變量並初始化爲0。我們可以發現下劃線後面跟了一個數字,這裏應該代表的是變量在局部變量表中的位置。
i:1
j:2
m:3
再關注main方法:6、7、10:
iload_1
表示將局部變量表中位置1的數據放入操作數棧中(這裏對應的是i,此時i的值爲0)然後pop出來賦值給j。然後再將i自增iinc
。最後istore_2
存儲j到局部變量表中。
操作完成後i=1,j=1。
最後關注main方法:11、14、15:
iinc
首先自增i,然後iload_1
將局部變量表中位置1的數據放入操作數棧中(這裏對應的是i,此時i的值爲2)然後pop出來賦值給m。最後istore_3
存儲m到局部變量表中。
操作完成後i=2,j=1,m=2。
這就是爲什麼大家都說,i++是先賦值後自增,而++i是先自增後賦值的原因。
這裏要說明的是,如果是單獨的i++、++i是沒有什麼區別的。