今天朋友去面試,遇到了一個非常"簡單"的題目,如下
a = 0; for(int i = 0; i < 10; i++) { a = a++; } System.out.println(a); //a = ?
當時老張就一臉懵逼的說,a不是等於10嗎??
朋友當時就說,培訓出來的都知道a最后等於0
此時老張陷入了深深的思索之中,按理說是a取值a=0,a++=1,然后a=a++,十次之后當然等於10啊,
於是老張打開IDE寫下如下代碼並運行:
class a{ public static void main(String [] args) { int a=0; for (int i = 0; i < 10; i++) { a=a++; } System.out.println(a); } } javac a.java java a.class
結果竟然真的是0,這個時候老張一度懷疑自己是個假程序員.
於是老張本着鑽牛角尖的精神,使用javap命令查看字節碼命令
javap -c a.class
得到如下代碼
Compiled from "a.java" class a { a(); Code: 0: aload_0 1: invokespecial #1 // Method java/lang/Object."<init>":()V 4: return public static void main(java.lang.String[]); Code: 0: iconst_0 1: istore_1 2: iconst_0 3: istore_2 4: iload_2 5: bipush 10 7: if_icmpge 21 10: iload_1 11: iinc 1, 1 14: istore_1 15: iinc 2, 1 18: goto 4 21: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream; 24: iload_1 25: invokevirtual #3 // Method java/io/PrintStream.println:(I)V 28: return }
一目了然,不過我們這樣看更簡單
class a{ public void test() { int a=0; a=a++;//a=0 } }
使用javap -c
Compiled from "a.java" class a { a(); Code: 0: aload_0 1: invokespecial #1 // Method java/lang/Object."<init>":()V 4: return public void test(); Code: 0: iconst_0 //a=0 //將int型的0推送至棧頂 1: istore_1 // //將int型的數值存入第二個本地變量 2: iload_1 // //將第二個int型本地變量推送至棧頂 3: iinc 1, 1 // //++操作 局部變量自增指令 6: istore_1 // //將int型的數值存入第二個本地變量 7: return }
Compiled from "a.java" class a { a(); Code: 0: aload_0 1: invokespecial #1 // Method java/lang/Object."<init>":()V 4: return public void test(); Code: 0: iconst_0 //a=0 //將int型的0推送至棧頂 1: istore_1 // //將int型的數值存入第二個本地變量 2: iload_1 // //將第二個int型本地變量推送至棧頂 3: iinc 1, 1 // //++操作 局部變量自增指令 6: istore_1 // //將int型的數值存入第二個本地變量 7: return }
與
class a{ public void test() { int a=0; a=++a; //a=1 } }
Compiled from "a.java" class a { a(); Code: 0: aload_0 1: invokespecial #1 // Method java/lang/Object."<init>":()V 4: return public void test(); Code: 0: iconst_0 1: istore_1 2: iinc 1, 1 5: iload_1 6: istore_1 7: return }
對比如下指令
a++
0: iconst_0 //a=0 //將int型的0推送至棧頂
1: istore_1 // //將int型的數值存入第二個本地變量
2: iload_1 // //將第二個int型本地變量推送至棧頂
3: iinc 1, 1 // //++操作 局部變量自增指令
6: istore_1 // //將int型的數值存入第二個本地變量
7: return
++a
0: iconst_0 //a=0 //將int型的0推送至棧頂
1: istore_1 // //將int型的數值存入第二個本地變量
2: iinc 1, 1 // //++操作 局部變量自增指令
5: iload_1 // //將第二個int型本地變量推送至棧頂
6: istore_1 // //將int型的數值存入第二個本地變量
7: return
所有至關重要的異步就是: iinc(局部變量自增)指令與iload_1指令的先后順序決定的a的值
那么問題又回到了,i++和++i先加后加的路子上,這句話好耳熟
詳細的說,現在我們一步步的分析下a=a++到底做了什么
1)inconst_0將0推送至棧頂
2)istore 將int型的數值存入第二個本地變量
3)iload_1 將第2個本地類型推送至棧頂
3)iinc 局部變量自增指令(注意是局部變量)
4)istore_1 將int型的值存入第二個局部變量表中(此時棧中的0將覆蓋局部變量表中的1)
所以a=a++之所以結果為0,應為局部變量自增的值被覆蓋了
所以a=++a的字節碼指令聰明的你肯定也懂了
再簡單的補充一下關於Java棧的一些知識
Java中棧是由棧幀組成,每個棧幀都是線程私有的.
每個棧幀包含4塊內容:
1)局部變量表
局部變量表用來存儲參數和局部變量,以slot為單位,除了long和double占2個slot,其余的6個基本類型和reference(引用)占1個slot
2)操作棧(操作數棧)
操作棧成為"基於棧的執行引擎",方法執行中進行算術運算或者是調用其他的方法進行參數傳遞的時候是通過操作數棧進行的
3)動態鏈接
略
4)返回地址
略
我的公眾號: