大家都知道i++跟++i的區(qū)別:
- i++是先賦值再運算
- ++i是先運算再賦值
那可能很多人沒有寫過i=i++
或者i=++i
哎垦,這樣的騷語句镜雨,這個時候是什么樣的情況呢纯趋?
可能很多人能想到i=i++
,結(jié)果i=0冷离,因為先賦值,這時候值是0纯命,所以i=0西剥。
而i=++i
最終結(jié)果i=1,因為是先運算+1再賦值亿汞,這時候已經(jīng)是1了瞭空。
那到底是做了什么樣的操作來實現(xiàn)這樣的結(jié)果的呢?我們來追根溯源疗我,正常情況咆畏,我們應(yīng)該先看一下class文件顯示什么東西,那我們寫了幾個最簡單的方法如下:
public class TestI {
public void testMethod() {
int i = 0;
i = i + 2;
}
public void testMethodA() {
int i = 0;
i = i++;
}
public void testMethodB() {
int i = 0;
i = ++i;
}
}
這里為了明白正常的i+1
在java中是怎么處理的吴裤,增加了一個i=i+2
的對照組旧找。
我們用javac TestI.java
編譯成class文件,如下:
public void testMethod() {
byte var1 = 0;
int var2 = var1 + 2;
}
public void testMethodA() {
byte var1 = 0;
int var2 = var1 + 1;
}
public void testMethodB() {
byte var1 = 0;
int var2 = var1 + 1;
}
testMethodA
竟然跟testMethodB
是一樣的麦牺。Emmm钮蛛,因吹斯聽,應(yīng)該是IDE在翻譯字節(jié)碼的時候沒有看出來這倆的區(qū)別剖膳?那我們還是來看字節(jié)碼好了魏颓。
用javap -c TestI.class
命令查看字節(jié)碼如下:
這里需要的知識儲備是JVM指令集和JVM 棧幀之操作數(shù)棧與局部變量表,默認(rèn)讀者了解不在贅述吱晒。
public class testJava.TestI {
public testJava.TestI();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public void testMethod();
Code:
0: iconst_0 //將int型0壓棧至棧頂
1: istore_1 //將棧頂int型數(shù)值存入第1個本地變量
2: iload_1 //將第1個int型本地變量壓棧至棧頂
3: iconst_2 //將int型2壓棧至棧頂
4: iadd //棧頂兩個數(shù)相加并進(jìn)棧
5: istore_1 //將棧頂int型數(shù)值存入第1個本地變量
6: return //從當(dāng)前方法返回void
public void testMethodA();
Code:
0: iconst_0 //將int型0壓棧至棧頂
1: istore_1 //將棧頂int型數(shù)值存入第1個本地變量
2: iload_1 //將第1個int型本地變量壓棧至棧頂
3: iinc 1, 1 //將指定int型變量增加指定值甸饱,可以有兩個變量,分別表示index, const仑濒,index指第index個int型本地變量叹话,const增加的值,所以是第1個本地變量增加1
6: istore_1 //將棧頂int型數(shù)值存入第1個本地變量
7: return //從當(dāng)前方法返回void
public void testMethodB();
Code:
0: iconst_0 //將int型0壓棧至棧頂
1: istore_1 //將棧頂int型數(shù)值存入第1個本地變量
2: iinc 1, 1 //將指定int型變量增加指定值躏精,第1個本地變量增加1
5: iload_1 //將第1個int型本地變量壓棧至棧頂
6: istore_1 //將棧頂int型數(shù)值存入第1個本地變量
7: return //從當(dāng)前方法返回void
}
我們做了一個最簡單的i=i+2
的對照組來看正常情況下這個最簡單的增量操作是長什么樣子的渣刷,它的具體流程是先把i在本地變量表里面初始化出來,再把i的值放到操作數(shù)棧矗烛,再給操作數(shù)棧放一個需要加的2辅柴,然后i跟2相加箩溃,得到的結(jié)果再存到本地變量完成相加操作。
而i = i++或者i = ++i的操作還是跟i=i+1有很大區(qū)別的碌嘀。最大的區(qū)別就是i=i+2
是用了iadd涣旨,而i++是用了iinc,iadd是作用在操作數(shù)棧中的股冗,而iinc是直接在本地變量表中直接把變量增加霹陡。
來看i++
和++i
,其實就是iinc
跟iload
的運行順序的區(qū)別止状,印證了我們之前所說的這兩者的區(qū)別烹棉,我們再贅述一遍兩者的區(qū)別,看是怎么體現(xiàn)出來的:
- i++是先賦值再運算
- ++i是先運算再賦值
i++是先iload_1
把0這個值推到操作數(shù)幀頂部怯疤,再iinc
把本地變量表里面的i做+1操作浆洗,這個操作結(jié)束后意味著這時候在操作數(shù)棧里面代表i的值仍然是+1之前的值也就是0,而其實本地變量表中的值已經(jīng)是1集峦,但是得下一個再iload1
的時候才是1代表i出戰(zhàn)伏社。
而++i,正好相反是先iinc
做+1操作塔淤,然后再代表i出證摘昌,這時候的值已經(jīng)變成了1。
所以后面執(zhí)行到i=
i++還是i=
++i的共同代碼i=
高蜂,在字節(jié)碼中也就是istore_1聪黎,把這時候棧中代表i出征的值賦值回本地變量表中的i,所以這時候i=i++操作數(shù)棧里面的0覆蓋了本地變量表中的1备恤。
所以i=i++比i++做得多此一舉事情就是多做了一個i=, 把操作數(shù)棧中的0覆蓋了本地變量表中的正確值1.
說一千道一萬挺举,什么都比不上一張圖來的直觀:
那既然i++是先賦值再運算,那我們多做幾次i=i++是不是就好了烘跺,比如加個while循環(huán)湘纵,like below:
public void testMethodC() {
int i = 0;
for(int j = 0;j<100;j++){
i = i++;
}
}
public void testMethodC();
Code:
0: iconst_0 //將int型0壓棧至棧頂
1: istore_1 //將棧頂int型數(shù)值存入第1個本地變量
2: iconst_0 //將int型0壓棧至棧頂
3: istore_2 //將棧頂int型數(shù)值存入第2個本地變量
4: iload_2 //將第2個int型本地變量推送至棧頂
5: bipush 100 //將將單字節(jié)的常量值100推送至棧頂
7: if_icmpge 21 //棧頂彈出兩個值,比較兩int型數(shù)值大小滤淳,當(dāng)結(jié)果大于等于0時跳轉(zhuǎn)到21
10: iload_1 //將第1個int型本地變量推送至棧頂
11: iinc 1, 1 //將指定int型變量增加指定值梧喷,第1個本地變量增加1
14: istore_1 //將棧頂int型數(shù)值存入第1個本地變量
15: iinc 2, 1 //將指定int型變量增加指定值,第2個本地變量增加1
18: goto 4 //跳轉(zhuǎn)到偏移位4
21: return
其實想一想還是0哈脖咐,因為每次的i都是重新load到操作數(shù)棧的铺敌,之前的+1過來的又會被覆蓋,不管循環(huán)多少次都是一樣的: