1. 下面是一到Java筆試題:
1 public class Test2 2 { 3 public void add(Byte b) 4 { 5 b = b++; 6 } 7 public void test() 8 { 9 Byte a = 127; 10 Byte b = 127; 11 add(++a); 12 System.out.print(a + " "); 13 add(b); 14 System.out.print(b + ""); 15 } 16 }
2. 為方便分析起見,將打印的語句去掉,如下:
1 public void add(Byte b) 2 { 3 b = b++; 4 } 5 public void test() 6 { 7 Byte a = 127; 8 Byte b = 127; 9 add(++a); 10 add(b); 11 }
3. 將上述代碼反編譯,得到如下字節碼:
1 public void add(java.lang.Byte); 2 Code: 3 0: aload_1 4 1: astore_2 5 2: aload_1 6 3: invokevirtual #2 // Method java/lang/Byte.byteValue:( 7 )B 8 6: iconst_1 9 7: iadd 10 8: i2b 11 9: invokestatic #3 // Method java/lang/Byte.valueOf:(B) 12 Ljava/lang/Byte; 13 12: dup 14 13: astore_1 15 14: astore_3 16 15: aload_2 17 16: astore_1 18 17: return 19 20 public void test(); 21 Code: 22 0: bipush 127 23 2: invokestatic #3 // Method java/lang/Byte.valueOf:(B) 24 Ljava/lang/Byte; 25 5: astore_1 26 6: bipush 127 27 8: invokestatic #3 // Method java/lang/Byte.valueOf:(B) 28 Ljava/lang/Byte; 29 11: astore_2 30 12: aload_0 31 13: aload_1 32 14: invokevirtual #2 // Method java/lang/Byte.byteValue:( 33 )B 34 17: iconst_1 35 18: iadd 36 19: i2b 37 20: invokestatic #3 // Method java/lang/Byte.valueOf:(B) 38 Ljava/lang/Byte; 39 23: dup 40 24: astore_1 41 25: invokevirtual #4 // Method add:(Ljava/lang/Byte;)V 42 28: aload_0 43 29: aload_2 44 30: invokevirtual #4 // Method add:(Ljava/lang/Byte;)V 45 33: return 46 }
4. 字節碼很長,看着發怵,不用怕,我們將字節碼分成兩部分:add方法和test方法。
5. 我們先來看add方法:
1 add方法局部變量表 2 下標: 0 1 2 3 3 標記: this 形參Byte b Byte型臨時變量tmp Byte型臨時變量tmp2 4 值 : -128 -128 -127 5 public void add(java.lang.Byte); 6 Code: 7 0: aload_1 // 局部變量表中下標為1的引用型局部變量b進棧 8 1: astore_2 // 將棧頂數值賦值給局部變量表中下標為2的引用型局部變量tmp,棧頂數值出棧。 9 2: aload_1 // 局部變量表中下標為1的引用型局部變量b進棧 10 3: invokevirtual #2 // 自動拆箱,訪問棧頂元素b,調用實例方法b.byteValue獲取b所指Byte 11 // 對象的value值-128,並壓棧 12 6: iconst_1 // int型常量值1進棧 13 7: iadd // 依次彈出棧頂兩int型數值1(0000 0001)、-128(1000 0000) 14 //(byte類型自動轉型為int類型)相加,並將結果-127(1000 0001)進棧 15 8: i2b // 棧頂int值-127(1000 0001)出棧,強轉成byte值-127(1000 0001),並且結果進棧 16 9: invokestatic #3 // 自動裝箱:訪問棧頂元素,作為函數實參傳入靜態方法Byte.valueOf(byte), 17 // 返回value值為-127的Byte對象的地址,並壓棧 18 12: dup // 復制棧頂數值,並且復制值進棧 19 13: astore_1 // 將棧頂數值賦值給局部變量表中下標為1的引用型局部變量b,棧頂數值出棧。此時b為-127 20 14: astore_3 // 將棧頂數值賦值給局部變量表中下標為3的引用型局部變量tmp2,棧頂數值出棧。此時tmp2為-127 21 15: aload_2 // 局部變量表中下標為2的引用型局部變量tmp進棧,即-128入棧 22 16: astore_1 // 將棧頂數值賦值給局部變量表中下標為1的引用型局部變量b,棧頂數值出棧。此時b為-128 23 17: return
總結一下上述過程,核心步驟為b = b++;分為三步:參考:http://blog.csdn.net/brooksychen/article/details/1624753
①把變量b的值取出來,放在一個臨時變量里(我們先記作tmp);
②把變量b的值進行自加操作;
③把臨時變量tmp的值作為自增運算前b的值使用,在本題中就是給變量b賦值。
到此可得出結論,add方法只是個擺設,沒有任何作用,不修改實參的值。
6. 搞懂了add方法,我們接下來分析test方法:
這里需要說明兩點:
(1)由於Byte類緩存了[-128,127]之間的Byte對象,故當傳入的實參byte相同時,通過Byte.valueOf(byte)返回的對象是同一個對象,詳見Byte源碼。
(2)如果是實例方法(非static),那么局部變量表的第0位索引的Slot默認是用於傳遞方法所屬對象實例的引用,在方法中通過this訪問。詳見:http://wangwengcn.iteye.com/blog/1622195
1 test方法局部變量表 2 下標: 0 1 2 3 標記: this 形參Byte a Byte型臨時變量b 4 值 : -128 127 5 public void test(); 6 Code: 7 0: bipush 127 // 將一個byte型常量值推送至操作數棧棧頂 8 2: invokestatic #3 // 自動裝箱:訪問棧頂元素,作為函數實參傳入靜態方法Byte.valueOf(byte), 9 // 返回value值為127的Byte對象的地址,並壓棧 10 5: astore_1 // 將棧頂數值賦值給局部變量表中下標為1的引用型局部變量a,棧頂數值出棧。此時a為127 11 6: bipush 127 // 將一個byte型常量值推送至操作數棧棧頂 12 8: invokestatic #3 // 自動裝箱:訪問棧頂元素,作為函數實參傳入靜態方法Byte.valueOf(byte), 13 // 返回value值為127的Byte對象的地址,並壓棧。這里需要說明一點, 14 // 由於Byte類緩存了[-128,127]之間的Byte對象,故當傳入的實參byte相同時, 15 // 通過Byte.valueOf(byte)返回的對象是同一個對象,詳見Byte源碼。 16 11: astore_2 // 將棧頂數值賦值給局部變量表中下標為2的引用型局部變量b,棧頂數值出棧。此時b為127 17 12: aload_0 // 局部變量表中下標為0的引用型局部變量進棧,即this,加載this主要是為了下面通過this調用add方法。 18 13: aload_1 // 局部變量表中下標為1的引用型局部變量a進棧 19 14: invokevirtual #2 // 自動拆箱,訪問棧頂元素a,調用實例方法a.byteValue獲取a所指Byte 20 // 對象的value值127,並壓棧 21 17: iconst_1 // int型常量值1進棧 22 18: iadd // 依次彈出棧頂兩int型數值1(0000 0001)、127(0111 1111) 23 //(byte類型自動轉型為int類型)相加,並將結果128(1000 0000)進棧 24 19: i2b // 棧頂int值128(1000 0000)出棧,強轉成byte值-128(1000 0000),並且結果進棧 25 20: invokestatic #3 // 自動裝箱:訪問棧頂元素,作為函數實參傳入靜態方法Byte.valueOf(byte), 26 // 返回value值為-128的Byte對象的地址,並壓棧 27 23: dup // 復制棧頂數值,並且復制值進棧 28 24: astore_1 // 將棧頂數值賦值給局部變量表中下標為1的引用型局部變量a,棧頂數值出棧。此時a為-128 29 25: invokevirtual #4 // 調用實例方法add:(Byte),傳入的實參為棧頂元素,也即a的拷貝,前面已經分析過了,該調用不改變a的對象值 30 // 該實例方法的調用需要訪問棧中的兩個參數,一個是實參,也即a的拷貝,一個是在第12步入棧的this。 31 28: aload_0 // 局部變量表中下標為0的引用型局部變量進棧,即this,加載this主要是為了下面通過this調用add方法。 32 29: aload_2 // 局部變量表中下標為2的引用型局部變量b進棧 33 30: invokevirtual #4 // 調用實例方法add:(Byte),傳入的實參為棧頂元素,也即b,前面已經分析過了,該調用不改變b的對象值 34 // 該實例方法的調用需要訪問棧中的兩個參數,一個是實參,也即b,一個是在第28步入棧的this。 35 33: return // 函數執行到最后,b所指對象的值沒有改變,仍為127。 36 }
7. 綜合以上分析,原問題的輸出為-128 127
8. 小結:
通過以上分析,我們發現該題綜合考察了Byte自動拆/裝箱、Byte對象緩存、Java編譯器對i=i++的特殊處理等等,相當有難度呀。