從字節碼角度分析Byte類型變量b++和++b


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++的特殊處理等等,相當有難度呀。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM