文章目錄
- 1.第一個問題
- 2.從位元組碼的角度分析:
- 3.關于效率
- 4.例題分析
- 參考網址
1.第一個問題
之前隻知道,i++是先指派再自增,++i是先自增再指派,那麼如果是下面這種情況會輸出什麼呢
int i = 0;
i = i++;
System.out.println(i);
會輸出多少?
答案是0
2.從位元組碼的角度分析:
先說結論:不論是i++還是++i,都有個共同點是先自增。
對于:i++,先自增,再用之前的值給目标變量指派
對于:++i,先自增,再用自增之後的值給目标變量指派
ok,下面從位元組碼的角度講清楚為什麼是這樣:
首先分析剛才的代碼:檔案:Test.java
public class Test {
public static void main(String[] args) {
int i = 0;
i = i++;
System.out.println(i);
}
}
終端
(Terminal)
進入檔案所在檔案夾,利用
javac Test.java
指令将.java編譯成位元組碼.class檔案,然後利用
javap -v Test.class
輸出帶詳細資訊的反彙編
![](https://img.laitimes.com/img/_0nNw4CM6IyYiwiM6ICdiwiIyVGduV2YfNWawNyZuBnL4kTN0ADMycTM1ATMxAjMwIzLc52YucWbp5GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.png)
這裡隻看code部分:
//iconst_<x>, 将常量x加載到操作數棧中
//istore_<n>,将操作數棧中的頂部數值存儲到局部變量表中索引為n(slot_n)的位置
//iload_<n>: 将(slot_n)上的值加載到操作數棧Code:
Code:
stack=2, locals=2, args_size=1 //棧深度為2,操作數個數為2(包括了args)
0: iconst_0 // 将常量0加載到操作數棧中
1: istore_1 //将操作數棧中的頂部數值存儲到slot_1的位置
2: iload_1 //slot_1上的值加載到操作數棧
3: iinc 1, 1 //iinc用于實作局部變量的自增操作。在所有位元組碼指令中,隻有該指令可直接用于操作局部變量
//iinc slot_ , number ,即對指定slot_n的變量進行+=number的操作,直接對局部變量表的元素進行累加,而不是在棧中。
6: istore_1 //将操作數棧中的頂部數值存儲到slot_1的位置
7: getstatic #7 // Field java/lang/System.out:Ljava/io/PrintStream;
10: iload_1 //slot_1上的值加載到操作數棧
11: invokevirtual #13 // Method java/io/PrintStream.println:(I)V
14: return
畫圖表示:
注意:
iinc
是直接對
局部變量表
的元素進行累加,而不是在棧中
重點在于2、3兩步, 先把原來的值0加載到操作數棧,然後對局部變量表中的i自增;【第3步iinc包括了三條指令】然後第6步将操作數棧中的值(原來的值)指派給局部變量表中的目标變量(i)
是以是
先自增再指派
,隻不過把沒自增之前的值先暫時儲存到了操作數棧中,然後用其指派,是以
看起來好像是先指派再自增一樣
那如果是++i呢?
public class Test {
public static void main(String[] args) {
int i = 0;
i = ++;
System.out.println(i);
}
}
//輸出:1
反彙編:
Code:
stack=2, locals=2, args_size=1
0: iconst_0
1: istore_1
2: iinc 1, 1
5: iload_1
6: istore_1
7: getstatic #7 // Field java/lang/System.out:Ljava/io/PrintStream;
10: iload_1
11: invokevirtual #13 // Method java/io/PrintStream.println:(I)V
14: return
重點還是在2、3兩步,這次是先對局部變量表中的i(0)自增,再把自增後的值(1)加載到操作數棧;然後第6步将操作數棧中的值(自增後的值)指派給局部變量表中的目标變量(i);
是以,++i和i++都是先自增再指派,指派都是将操作數棧中的值給局部變量表中的目标變量指派,不同的是2、3兩步中,一個是将自增之前的值放進操作數棧,一個是将自增之後的值放進操作數棧。
那麼我們給另外的變量指派也是這樣嗎?是的
public class Test {
public static void main(String[] args) {
int i = 0;
int a = 0;
a = i++;
System.out.println(i);
}
}
反彙編:
Code:
stack=2, locals=3, args_size=1
0: iconst_0
1: istore_1
2: iconst_0
3: istore_2
4: iload_1
5: iinc 1, 1
8: istore_2
9: getstatic #7 // Field java/lang/System.out:Ljava/io/PrintStream;
12: iload_1
13: invokevirtual #13 // Method java/io/PrintStream.println:(I)V
16: return
再繼續探究,i=i+1呢?
int i = 0;
i = i + 1;
System.out.println(i);
反彙編:把iinc變成了3、4兩步,在操作數棧上完成
iinc指令存在意義
Code:
stack=2, locals=2, args_size=1
0: iconst_0
1: istore_1
2: iload_1
3: iconst_1
4: iadd
5: istore_1
6: getstatic #7 // Field java/lang/System.out:Ljava/io/PrintStream;
9: iload_1
10: invokevirtual #13 // Method java/io/PrintStream.println:(I)V
13: return
3.關于效率
之前看過一篇文章說++i比i++效率高,具體原因是:
i++:取出i,複制i,增加i,傳回副本;
++i:取出i,增加i,傳回i;
i++要增加一個副本,無疑是要多耗記憶體,效率要低一點。
然後今天又看到有人說對于底層c語言來說是這樣的,還有對于c++來說也是++i比i++多了一步
class INT;
//++i 的版本
INT INT::operator++()
{
*this = *this + 1;
return *this;
}
//i++ 的版本
const INT INT::operator ++(int)
{
INT oldvalue = *this;
*this = *this + 1;
return oldvalue;
}
但是對于java來說,從上一節的分析就能看出,他們的位元組碼是一樣的,都是 iinc,是以效率上沒有差别
4.例題分析
先聲明:一個變量也是表達式,多個表達式的加減法運算都是從左到右進行的
int a = 0;
int b = a + a * 2;
因為有乘法,是以
a * 2
優先組成表達式,而不是
a + a
組成表達式,也就是說總體上可以分為兩個表達式:
“a” 表達式 和 “a * 2” 表達式
,這兩個表達式相加肯定
從左到右
計算,先算完a表達式的結果,再算a * 2表達式的結果。
例1
int i = 0;
i = i++;
System.out.println("i = " + i);
//0
例2
int a = 2;
int b = (3 * a++) + a;
System.out.println(b);
//9
int b = (3 * a++) + a;a++後,a = 3,并傳回自增之前的值2,是以此時表達式為:
int b = (3 * 2) + a;此時a的值已經是3了,表達式又變為:
int b = (3 * 2) + 3; 是以b = 9
例3
int a = 2;
int b = a + (3 * a++);
System.out.println(b);
//8
一個變量也是表達式,多個表達式的加減法運算都是從左到右進行的,這個理論可能不能深刻體會,但是把代碼稍微改一下就能了解了,如下:
int b = (a * 1) + (3 * a++) 這個代碼和 int b = a + (3 * a++) 是一樣的,沒有差別,但是看(a *1)你就很容易的知道要先算a * 1這個表達式,表達式的結果為2。
是以,雖然 int b = a + (3 * a++) 中前面的a隻是一個變量,但他也是一個表達式,a這個表達式和(3 * a++)這個表達式進行相加,多個表達式的運算都是
從左到右
進行的,是以先算a這個表達式,a表達式計算結果為2,是以表達式變成:
int b = 2 + (3 * a++) 然後是a自增并傳回自增之前的值2,是以表達式又變為:
int b = 2 + (3 * 2);是以結果為8。此時a的值為3
從位元組碼角度看呢:
Code:
stack=3, locals=3, args_size=1
0: iconst_2
1: istore_1
2: iload_1
3: iconst_3
4: iload_1
5: iinc 1, 1
8: imul //iadd: 将操作數棧中的前兩個int值出棧并相乘,然後将相乘的結果放入操作數棧
9: iadd //iadd: 将操作數棧中的前兩個int值出棧并相加,然後将相加的結果放入操作數棧
10: istore_2
11: getstatic #7 // Field java/lang/System.out:Ljava/io/PrintStream;
14: iload_2
15: invokevirtual #13 // Method java/io/PrintStream.println:(I)V
18: return
可以看出,是從左向右,先對左邊的a原來的值進行了入棧
例4
int i = 1;
int j = 1;
int k = i++ + ++i + ++j + j++;
System.out.println(k);
//8
表達式原則說多個表達式的加減法運算都是從左到右進行的,這裡的表達式有:i++、++i、++j、j++,都是加法,那我們就從左到右計算這4個表達式就OK了,如下:
1、先算i++,i++之後i的值為2,并傳回++之前的值1,是以整個表達式可以變為:
1 + ++i + ++j + j++; // 此時的i值為2
2、再計算++i,++i之後i的值為3,并傳回3,是以整個表達式可以變為:
1 + 3 + ++j + j++; // 此時i的值為3
3、再計算++j,++j之後j的值為2,并傳回2,是以整個表達式可以變為:
1 + 3 + 2 + j++; // 此時j的值為2
4、再計算j++,j++之後 j的值為3,并傳回2,是以整個表達式可以變為:
1 + 3 + 2 +2; // 結果為8,此時j的值為3
例5
int a = 0;
int b = 0;
a = a++;//a=0
b = a++;
System.out.println("a = " + a + ", b = " + b);
//a = 1, b = 0
a = a++; a++之後a的值為1,并傳回0,是以a的值由1又變回了0
b = a++; a++之後a的值為1,并傳回0,0指派給b,是以b為0,而a是1
例6
int a = 0;
for (int i = 0; i < 99; i++) {
a = a ++;
}
System.out.println(a);
//0
例7
int b = 0;
for (int i = 0; i < 99; i++) {
b = ++ b;
}
System.out.println(b);//99
例8
Integer a = 0;
int b = 0;
for (int i = 0; i < 99; i++) {
a = a ++;
b = a ++;
}
System.out.println(a);
System.out.println(b);
//a=99,b=98
參考網址
https://blog.csdn.net/android_cai_niao/article/details/106027313
java位元組碼閱讀:
https://juejin.im/post/6844904019941425165