你可能并不理解i++和++i
面對(duì)i++和++i,是不是經(jīng)常忘記兩者的區(qū)別?你是真的理解它還是只是靠死記硬背記住的它?如果你能以下面第一段的方式解釋它,那么可能還不是很理解 i++ ,如果要理解它需要你了解更多的知識(shí)。
public class Plus {
public static void main(String args[]) {
int num = 50;
num = num++ * 2;
System.out.println(num);
}
}
1. 表面
num++ 是將原值先拿出來,自身再+1,此時(shí)num=51,然后將 50*2=100 再賦值給num,所以100覆蓋了原來的51。
這么回答是對(duì)的,大部分人可能都會(huì)這么回答,但你還能再深入點(diǎn)嗎?
2. JVM字節(jié)碼指令解讀
要理解這一層次,你需要先了解一下JVM的內(nèi)存結(jié)構(gòu)-虛擬機(jī)棧
2.1 虛擬機(jī)棧
我們應(yīng)該清楚 JVM 內(nèi)存中有如下幾個(gè)部分:

我們主要要了解虛擬機(jī)棧中運(yùn)算是如何進(jìn)行的,這里著重看局部變量表和操作數(shù)棧:
「局部變量表」:用于存放各種基本數(shù)據(jù)類型或?qū)ο笠茫褪欠椒▋?nèi)定義的局部變量,其存儲(chǔ)單元為slot槽(double/long占兩個(gè)slot,其他均占一個(gè)slot);JVM會(huì)為局部變量表中的每一個(gè)slot都分配一個(gè)訪問索引,通過這個(gè)索引即可成功訪問到局部變量表中指定的局部變量值。

「操作數(shù)?!?/strong>:這是一個(gè)棧的數(shù)據(jù)結(jié)構(gòu),可入棧彈出,將局部變量表中的變量放入棧中或彈出,CPU在計(jì)算時(shí)會(huì)從棧中取出并計(jì)算,然后計(jì)算結(jié)果再入棧。

2.2 字節(jié)碼指令
這里列出一些會(huì)用到的字節(jié)碼指令:
| 指令 | 意義 |
|---|---|
| bipush | 將單字節(jié)的常量值(-128~127)推送至棧頂 |
| istore_n | 將棧頂int型數(shù)值存入第n個(gè)本地變量 |
| iload_n | 將第n個(gè)int型本地變量推送至棧頂 |
| Iinc | 局部變量自增指令 |
| iconst_n | 將int型常量n推送至棧頂(n<=5) |
| imul | 將棧頂兩int型數(shù)值相乘并將結(jié)果壓入棧頂 |
在IDEA中安裝bytecode插件,點(diǎn)擊 view -> show bytecode 將上面的程序轉(zhuǎn)為字節(jié)碼指令來看看他是如何做的:
L0
//將50推送至棧頂
BIPUSH 50
//將50取出存儲(chǔ)到第一個(gè)變量中
ISTORE 1
L1
//將第一個(gè)變量中50推送至棧頂
ILOAD 1
//將第一個(gè)變量進(jìn)行加1操作,此時(shí)局部變量num=51
IINC 1 1
//將常量2推送至棧頂,此時(shí)棧中有兩個(gè)數(shù)50,2
ICONST_2
//將棧中的數(shù)進(jìn)行相乘,然后放入棧頂 50*2 = 100
IMUL
//取出棧頂元素100存入第一個(gè)變量中,num = 100
ISTORE 1
如果你之前沒有了解過字節(jié)碼相關(guān)的東西,那么你看上面可能有些吃力,所以我制作了動(dòng)畫給你看(善良不?
):
動(dòng)畫解析:



更善良的是
:如果你想要上面動(dòng)畫的源文件,公眾號(hào)回復(fù) “ i++ ” 即可。
2.3 擴(kuò)展
如果將上面代碼中的 num++ 替換成 ++num 結(jié)果會(huì)怎么呢?
這兩者的不同在于 ILOAD 1 和 IINC 1 1 的順序,是先將變量放入操作數(shù)棧中再將變量加1 還是 先將變量加1再將變量放入操作數(shù)棧中。
