前言
我們平常工作中對某個數進行累加的時候,一般都是用i++
,但是我看HashMap和其他一些jdk裏面的代碼,用到自增基本上用的都是
++i
,這兩個有啥區別呢?我們都知道一個是i++先賦值再自增, 另一個是先自增再賦值
。我們只知其然,不知其所以然。
今天我嘗試給大家解釋一下,希望我能表達清楚吧。
測試代碼
首先列出我們的測試代碼,你可以想一下這個i
會打印幾。
i++
public static void main(String[] args) {
int i = 8;
i = i++;
System.out.println(i);
}
++i
public static void main(String[] args) {
int i = 8;
i = i++;
System.out.println(i);
}
我們通過測試可以知道 第一個main方法輸出的是8
,第二個輸出是9
,第二個我相信大家知道的不知道的都知道會輸出9
,
至於第一個爲啥會輸出8
,肯定有點懵。先要了解一點,方法執行都是以一個棧幀的形式放在虛擬機棧裏面
,虛擬機棧裏面存放的內容有,局部變量表,操作數棧,動態鏈接,以及方法的出口。執行其實都是執行一條條的指令出棧和入棧。下面我們通過工具來看main方法的指令集,這裏我們需要藉助一個idea的插件jclasslib Bytecode viewer
Jclasslib插件
安裝
在idea的plugins裏面搜索jclasslib Bytecode viewer,然後點擊安裝
- 使用
鼠標光標放要查看二進制碼的類名上,然後點擊view,下拉框下面會有一個新的選項
我們就可以看到這個類的二進制碼文件啦,我們找到main方法這一塊,我們可以看到這個方法裏面所有的指令我們把這些指令都列出來,一個個來解釋他們的意思
這是i++的main方法指令集
在解釋之前我們還需要先看下局部變量表的內容
具體指令意思可以查看
https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-6.html#jvms-6.5.istore_n
的官網
- bipush 8 :這個指令實際上是
bipush
代表的是我們平常說的壓棧,把8壓到棧裏面 - istore_1 : 使用的指令
istore_n
它從操作數堆棧中彈出,並且將局部變量中下標爲1的值(從上面的局部變量表中可以知道這個值是我們的i)設置爲value。 - iload_1 :
iload_n
將局部變量表n位置的值進行壓棧 - iinc 1 by 1 :
iinc
局部變量表1位置的值增加1(這一步是在局部變量表中執行,不會壓棧) - getstatic #2 <java/lang/System.out>:
getstatic
這個獲取系統的一個靜態變量 - invokevirtual #3 <java/io/PrintStream.println>:
invokevirtual
執行一個多態的方法 - 15 return :返回
用到的指令都解釋了一下,現在我們統一來解釋一下i++的指令執行流程
0 bipush 8
2 istore_1
3 iload_1
4 iinc 1 by 1
7 istore_1
8 getstatic #2 <java/lang/System.out>
11 iload_1
12 invokevirtual #3 <java/io/PrintStream.println>
15 return
1.把8壓到棧中
2.把8從棧中彈出來,賦值給i,這個時候i=8
3.把i = 8 又壓到棧中
4.i在局部變量表中自增,此時局部變量表中的爲9,這個過程不會壓棧所以此時棧中還是8
5.把8彈出來賦值給i所以就從9變爲了8
我們再來看下++i
的操作指令
0 bipush 8
2 istore_1
3 iinc 1 by 1
6 iload_1
7 istore_1
8 getstatic #2 <java/lang/System.out>
11 iload_1
12 invokevirtual #3 <java/io/PrintStream.println>
15 return
1.把8壓到棧中
2.把8從棧中彈出來,賦值給i,這個時候i=8
3.i自增爲9,此時把9壓到棧中
4.再把9出棧,賦值給i 所以i = 9
總結
因爲i++和++i的指令集中,一個是先壓棧,再執行i++的指令,而++操作不會壓棧,所以棧中的i還是之前存進去的8,而++i是先執行自增指令,再去壓棧,再從棧中彈出賦值給i。