之前在做findbugs的時候經常會查看字節碼,都是一知半解的
那天看到一個問題,剛好可以從字節碼來解釋
這些題目常見於面試題,通常要是誰敢這麼寫代碼,完全是在找死。。
- public class Test {
- public static void main(String[] args) {
- int i=0;
- i=i++;
- System.out.println(i);
- }
- }
public class Test {
public static void main(String[] args) {
int i=0;
i=i++;
System.out.println(i);
}
}
大家說這裏爲什麼會輸出0呢,這裏我們從字節碼的角度解釋
通過反編命令javap –c classname來獲得字節碼
- Compiled from "Test.java"
- public class Test extends java.lang.Object{
- public Test();
- Code:
- 0: aload_0
- 1: invokespecial #8; //Method java/lang/Object."<init>":()V
- 4: return
- public static void main(java.lang.String[]);
- Code:
- 0: iconst_0
- 1: istore_1
- 2: iload_1
- 3: iinc 1, 1
- 6: istore_1
- 7: getstatic #16; //Field java/lang/System.out:Ljava/io/PrintStream;
- 10: iload_1
- 11: invokevirtual #22; //Method java/io/PrintStream.println:(I)V
- 14: return
- }
Compiled from "Test.java"
public class Test extends java.lang.Object{
public Test();
Code:
0: aload_0
1: invokespecial #8; //Method java/lang/Object."<init>":()V
4: return
public static void main(java.lang.String[]);
Code:
0: iconst_0
1: istore_1
2: iload_1
3: iinc 1, 1
6: istore_1
7: getstatic #16; //Field java/lang/System.out:Ljava/io/PrintStream;
10: iload_1
11: invokevirtual #22; //Method java/io/PrintStream.println:(I)V
14: return
}
這裏我們主要看main方法裏面的,附上解釋
- 0: iconst_0 這裏是聲明瞭一個常量0,並壓入堆棧
- 1: istore_1 將常量0彈出賦值給本地便量表index爲1位置的變量(也就是i)
- 以上完成了int i=0
- 2: iload_1 把本地變量表index爲1的位置的值(也就是i的值)壓入堆棧
- 3: iinc 1, 1 i的值加1也就是將本地便量表index爲1的位置的值加1,但是相應的堆棧裏面沒有加
- 上完成了i++
- 6: istore_1 將堆棧裏面的0彈出賦值給i(也就是0賦值給了i)
- 裏完成了i=i++
0: iconst_0 這裏是聲明瞭一個常量0,並壓入堆棧
1: istore_1 將常量0彈出賦值給本地便量表index爲1位置的變量(也就是i)
以上完成了int i=0
2: iload_1 把本地變量表index爲1的位置的值(也就是i的值)壓入堆棧
3: iinc 1, 1 i的值加1也就是將本地便量表index爲1的位置的值加1,但是相應的堆棧裏面沒有加
以上完成了i++
6: istore_1 將堆棧裏面的0彈出賦值給i(也就是0賦值給了i)
這裏完成了i=i++
總的來說是因爲在執行++的之前會先把i的值放入棧保存,然後在執行++,但是++僅改變了值不會改變堆棧裏面的值,所以當執行完畢後,彈出堆棧的值仍然是0,所以i最後還是0
jvm的字節碼就像C的彙編,會在根本上解決很多問題,另外熟悉也會提高我們代碼效率
以下代碼X可爲a、d、f、l或者i。
其中a指引用、b指布爾類型、c指字符、d指雙精度類型、f指浮點類型、i指整型、l指長整型、s指短整型。
堆棧操作。
pop、pop2:將堆棧的值彈出。pop2用來彈出64位的值(long、double),pop彈出32位的。
const_null將null的引用推送至堆棧。
bipush將單字節的常量值(-127~128)推送至堆棧。
sipush將一個短整型類型的常量值(-32K~32K)推送至堆棧。
ldc將常量值從常量池中推送至堆棧。
Xload,是將一個本地的X類型(參數或變量)推送至堆棧。
Xstore,將堆棧頂端的X類型的值彈出並放入本地分片中。
Xconst_Y 用來將X類型的常量Y值推送至堆棧。如,iconst_0就是將整數常量0推送至堆棧中。
分支與控制流。
nop,什麼也不做。
if(條件),條件可以是null(==null)、notnull(!=null)、eq、ne、gt、lt、_icmpeq、_icmpne……。
goto N。跳至指令號N。
return和Xreturn。X可以爲a、d、f、l或者i,從當前調用方返回,將堆棧頂端作爲X類型返回。