Java自增 本文分為以下部分: 慄子 慄子解釋 來點複雜的 位元組碼解讀 總結 慄子 java存在一種神奇的操作符,,自增1,但是經常分不清楚 **i** 和**++i** 兩者的區別,雖然最後結果可能都是 i+1,但是在不同場景使用有不同效果。先上一段代碼。 public class Increa ...
Java自增
本文分為以下部分:
慄子
java存在一種神奇的操作符,++,自增1,但是經常分不清楚 i++ 和++i 兩者的區別,雖然最後結果可能都是 i+1,但是在不同場景使用有不同效果。先上一段代碼。
public class IncreaseTest {
public static void main(String[] args) {
int i = 10;
int j = i++;
System.out.println(j);
int k = ++i;
System.out.println(k);
}
}
看著腦袋都大,感覺 j、k 最後值都一樣,實際上不一樣。在講解原理之前,先簡單說明一下底層東西。
局部變數表
其中第二段解釋,byte、char、short、int 等基本數據類型值會存在局部變數表中。
操作數棧
oracle java 操作數棧 中解釋
簡單理解就是 主要用於保存計算過程的中間結果,同時作為計算過程中變數臨時的存儲空間。就是用於計算等操作。
舉個簡單的慄子簡單解釋上面兩個東西
/**
* 操作數棧壓入 10 這個值
* 然後將 10 保存到本地變數表賦值給 i
* j同理
*
* 在進行 k = i + j 的操作
* 操作數棧從本地變數表中讀取 i 的值壓入操作數棧
* 操作數棧從本地變數表中讀取 j 的值壓入操作數棧
* 然後對操作數棧中的兩個值進行相加操作
* 將結果保存到本地變數表同時賦值給 k
* 最後輸出 本地變數表中 讀取的數據
*/
public static void main(String[] args) {
int i = 10;
int j = 20;
int k = i + j;
System.out.println(k);
}
圖例如下:
慄子解釋
回歸正題,最初的慄子,在計算 j 的時候,是先將 i 的本地變數表的值取出來壓入操作數棧,然後再進行 ++ 、賦值等操作,那是先賦值還是先 ++ 呢,其實已經不重要了,因為 ++ 操作操作的是本地變數表的值,而不是操作數棧中的值,所以當 i 的值已經壓入操作數棧後,那麼操作數棧中的值已經是 10 了,就算本地變數表的值再變化,不會改變操作數棧中的值。所以 j 為 10,本地變數表中 i 為11。(事實上是i的值取出來壓入操作數棧,然後i的本地變數表進行+1操作,然後操作數棧賦值給j)
然後第二步,進行的是 int k = ++i;
首先看到的是 ++,所以操作的是本地變數表 i+1 ,然後再將 i 的本地變數表值壓入操作數棧,再賦值給 k。
所以 i++ ,是先將 i 的值壓入操作數棧,再將本地變數表中 i 的值 +1 ,再將操作數棧中的數值賦值給要賦值的對象。
++i ,是先將本地變數表中 i 的值 +1,再壓入操作數棧中,再進行賦值給對象等操作。
簡記:先++就是先+1,後++就是後+1。
來點複雜的
public class IncreaseTest2 {
public static void main(String[] args) {
int i = 10;
i = i++;
System.out.println(i);
i = ++i;
System.out.println(i);
}
}
首先看 i = i++;
通過簡單的慄子,我們知道,當 i++ 再後面時,為後 ++;那麼是 i 先賦值給自己 ,再 +1?
當然不是,仔細看上方賦值給 j 的圖會發現,先本地變數 +1 ,再進行賦值。
然後再看 ++i,和第一個慄子差不多
位元組碼解讀
第一個慄子
在終端中使用 javap -v -p xxx.class 文件可以看到該class文件的位元組碼文件。由於不好閱讀,所以追加覆蓋到 test1.txt 文件中。(採用idea的 jclasslib 插件也可以)
在文件中找到熟悉的public static void main,下麵即為代碼的位元組碼,我貼一部分
感覺有點晦澀難懂,我一一解釋吧。
bipush 將數值(當前10位byte,-128—127均為byte)壓入操作數棧。
istore 將操作數棧棧頂的值彈出返回給本地變數表(保存到本地變數)。
iload 從本地變數表中讀取數值壓入操作數棧。
iinc 本地變數表中的值+1
0: bipush 10 //操作數棧壓入10
2: istore_1 //將10存儲到本地變數i (這裡1表示i,可以從文件下方LocalVariableTable查看)
3: iload_1 //讀取i的值 10 壓入操作數棧
4: iinc 1, 1 //本地變數中 i 的值 +1(操作數棧值不變,依舊為10)
7: istore_2 //操作數棧棧頂(10)的值彈出返回本地變數 j ,所以 j 為10
11: iload_2 //有個列印輸出流,需要讀取 j 的值,刪除了相應位元組碼
15: iinc 1, 1 //本地變數 i 的值 +1(原本為11,現在變為12)
18: iload_1 //讀取i的值 12 壓入操作數棧
19: istore_3 //保存至 k ,所以 k 為12
第二個慄子
使用同樣方法查看位元組碼
其實和第一個慄子一樣,可以自行理解。
特慄
public class IncreaseTest3 {
public static void main(String[] args) {
int i = 10;
i = (i++) * (++i);
//120
System.out.println(i);
}
}
其實再理解上方位元組碼或者圖解,這個自然而然容易理解了
圖解
位元組碼大概描述就是
iload //10 入操作數棧
iinc //本地變數 +1 (操作數棧中依舊為10)
innc //本地變數繼續 +1(此時為12)
iload //12 入操作數棧
//然後進行乘法得到120
istore
總結
i++ ,是先將 i 的值壓入操作數棧,再將本地變數表中 i 的值 +1 ,最後操作數棧的數進行操作。
++i ,是先將本地變數表中 i 的值 +1,再壓入操作數棧中,最後操作數棧的數進行操作。
簡單記憶:先++就是先+1,後++就是後+1。