Java自動裝箱與拆箱的實現分析

      原文地址:http://blog.csdn.net/jairuschan/article/details/7513045

   自裝箱(Autoboxing)

          大家在平時編寫Java程序時,都常常以以下方式來定義一個Integer對象:

[java] view plaincopy
  1. Integer i=100;  

          從上面的代碼中,大家可以得知,i爲一個Integer類型的引用,100爲Java中的基礎數據類型(primitive data type)。而這種直接將一個基礎數據類型傳給其相應的封裝類(wrapper class)的做法,便是自動裝箱(Autoboxing)。

          在jdk 1.5中,自動裝箱首次被引入。而在jdk 1.5之前,如果你想要定義一個value爲100的Integer對象,則需要這樣做:

[java] view plaincopy
  1. Integer i=new Integer (100);  

原理

          我們在以上代碼“Integer i=100;”處打一個斷點,跟蹤一下。

           

          接下來,我們可以看到,程序跳轉到了Integer類的valueOf(int i)方法中

[java] view plaincopy
  1. /** 
  2.      * Returns a <tt>Integer</tt> instance representing the specified 
  3.      * <tt>int</tt> value. 
  4.      * If a new <tt>Integer</tt> instance is not required, this method 
  5.      * should generally be used in preference to the constructor 
  6.      * {@link #Integer(int)}, as this method is likely to yield 
  7.      * significantly better space and time performance by caching 
  8.      * frequently requested values. 
  9.      * 
  10.      * @param  i an <code>int</code> value. 
  11.      * @return a <tt>Integer</tt> instance representing <tt>i</tt>. 
  12.      * @since  1.5 
  13.      */  
  14.     public static Integer valueOf(int i) {  
  15.         if(i >= -128 && i <= IntegerCache.high)  
  16.             return IntegerCache.cache[i + 128];  
  17.         else  
  18.             return new Integer(i);  
  19.     }  

          換句話說,裝箱就是jdk自己幫你完成了調用Integer.valueOf(100)。

拆箱(Unboxing)

[java] view plaincopy
  1. Integer integer100=100;  
  2. int int100=integer100;  

          從上面的代碼中,大家可看出integer100爲一個Integer類型的引用,int100爲一個int類型的原始數據類型。但是,我們可以將一個Integer類型的對象賦值給其相應原始數據類型的變量。這便是拆箱。

          拆箱與裝箱是相反的操作。裝箱是將一個原始數據類型賦值給相應封裝類的變量。而拆箱則是將一個封裝類的變量賦值給相應原始數據類型的變量。裝箱、拆箱的名字也取得相當貼切。

原理

          筆者相信大家也都猜到了,拆箱過程中jdk爲我們做了什麼。我們還是通過實驗來證明我們的猜想吧。

          在以上代碼的第二行代碼打上斷點,即在“int int100=integer100;”上打上斷點,跟蹤一下。

          我們可以看到,程序跳轉到了Integer的intValue()方法。

[java] view plaincopy
  1. /** 
  2.      * Returns the value of this <code>Integer</code> as an 
  3.      * <code>int</code>. 
  4.      */  
  5.     public int intValue() {  
  6.     return value;  
  7.     }  

          也就是,jdk幫我們完成了對intValue()方法的調用。對於以上的實驗而言,便是調用integer100的intValue()方法,將其返回值賦給了int100。

實驗1

[java] view plaincopy
  1. Integer integer400=400;  
  2. int int400=400;  
  3. System.out.println(integer400==int400);  

          在以上代碼的第三行中,integer400與int400執行了==運行。而這兩個是不同類型的變量,到底是integer400拆箱了,還是int400裝箱了呢?運行結果是什麼呢?

          ==運算是判斷兩個對象的地址是否相等或者判斷兩個基礎數據類型的值是否相等。所以,大家很容易推測到,如果integer400拆箱了,則說明對比的是兩個基礎類型的值,那此時必然相等,運行結果爲true;如果int400裝箱了,則說明對比的是兩個對象的地址是否相等,那此時地址必然不相等,運行結果爲false。(至於爲什麼筆者對它們賦值爲400,就是後面將要講到的陷阱有關)。

          我們實際的運行結果爲true。所以是integer400拆箱了。對代碼跟蹤的結果也證明這一點。

實驗2

[java] view plaincopy
  1. Integer integer100=100;  
  2. int int100=100;  
  3. System.out.println(integer100.equals(int100));  

          在以上代碼的第三行中,integer100的方法equals的參數爲int100。我們知道equals方法的參數爲Object,而不是基礎數據類型,因而在這裏必然是int100裝箱了。對代碼跟蹤的結果也證明了這一點。

          其實,如果一個方法中參數類型爲原始數據類型,所傳入的參數類型爲其封裝類,則會自動對其進行拆箱;相應地,如果一個方法中參數類型爲封裝類型,所傳入的參數類型爲其原始數據類型,則會自動對其進行裝箱。

實驗3

[java] view plaincopy
  1. Integer integer100 = 100;  
  2. int int100 = 100;  
  3. Long long200 = 200l;  
  4. System.out.println(integer100 + int100);  
  5. System.out.println(long200 == (integer100 + int100));  
  6. System.out.println(long200.equals(integer100 + int100));  

          在第一個實驗中,我們已經得知,當一個基礎數據類型與封裝類進行==運算時,會將封裝類進行拆箱。那如果+、-、*、/呢?我們在這個實驗中,就可知道。

          如果+運算,會將基礎數據類型裝箱,那麼:

  • 第4行中,integer100+int100就會得到一個類型爲Integer且value爲200的對象o,並執行這個對象的toString()方法,並輸出”200”;
  • 第5行中,integer100+int100就會得到一個類型爲Integer且value爲200的對象o,==運算將這個對象與long200對象進行對比,顯然,將會輸出false;
  • 第6行中,integer100+int100就會得到一個類型爲Integer且value爲200的對象o,Long的equals方法將long200與o對比,因爲兩都是不同類型的封裝類,因而輸出false;

          如果+運算,會將封裝類進行拆箱,那麼:

  • 第4行中,integer100+int100就會得到一個類型爲int且value爲200的基礎數據類型b,再將b進行裝箱得到o,執行這個對象的toString()方法,並輸出”200”;
  • 第5行中,integer100+int100就會得到一個類型爲int且value爲200的基礎數據類型b1,==運算將long200進行拆箱得到b2,顯然b1==b2,輸出true;
  • 第6行中,integer100+int100就會得到一個類型爲int且value爲200的基礎數據類型b,Long的equals方法將b進行裝箱,但裝箱所得到的是類型爲Integer的對象o,因爲o與long200爲不同的類型的對象,所以輸出false;

          程序運行的結果爲:      

[java] view plaincopy
  1. 200  
  2. true  
  3. false  

          因而,第二種推測是正確,即在+運算時,會將封裝類進行拆箱。

陷阱

陷阱1

[java] view plaincopy
  1. Integer integer100=null;  
  2. int int100=integer100;  

          這兩行代碼是完全合法的,完全能夠通過編譯的,但是在運行時,就會拋出空指針異常。其中,integer100爲Integer類型的對象,它當然可以指向null。但在第二行時,就會對integer100進行拆箱,也就是對一個null對象執行intValue()方法,當然會拋出空指針異常。所以,有拆箱操作時一定要特別注意封裝類對象是否爲null。

陷阱2

[java] view plaincopy
  1. Integer i1=100;  
  2. Integer i2=100;  
  3. Integer i3=300;  
  4. Integer i4=300;  
  5. System.out.println(i1==i2);  
  6. System.out.println(i3==i4);  

          因爲i1、i2、i3、i4都是Integer類型的,所以我們想,運行結果應該都是false。但是,真實的運行結果爲“System.out.println(i1==i2);”爲 true,但是“System.out.println(i3==i4);”爲false。也就意味着,i1與i2這兩個Integer類型的引用指向了同一個對象,而i3與i4指向了不同的對象。爲什麼呢?不都是調用Integer.valueOf(int i)方法嗎?

          讓我們再看看Integer.valueOf(int i)方法。

[java] view plaincopy
  1. /** 
  2.      * Returns a <tt>Integer</tt> instance representing the specified 
  3.      * <tt>int</tt> value. 
  4.      * If a new <tt>Integer</tt> instance is not required, this method 
  5.      * should generally be used in preference to the constructor 
  6.      * {@link #Integer(int)}, as this method is likely to yield 
  7.      * significantly better space and time performance by caching 
  8.      * frequently requested values. 
  9.      * 
  10.      * @param  i an <code>int</code> value. 
  11.      * @return a <tt>Integer</tt> instance representing <tt>i</tt>. 
  12.      * @since  1.5 
  13.      */  
  14.     public static Integer valueOf(int i) {  
  15.         if(i >= -128 && i <= IntegerCache.high)  
  16.             return IntegerCache.cache[i + 128];  
  17.         else  
  18.             return new Integer(i);  
  19.     }  

          我們可以看到當i>=-128且i<=IntegerCache.high時,直接返回IntegerCache.cache[i + 128]。其中,IntegerCache爲Integer的內部靜態類,其原碼如下:

[java] view plaincopy
  1. private static class IntegerCache {  
  2.         static final int high;  
  3.         static final Integer cache[];  
  4.   
  5.         static {  
  6.             final int low = -128;  
  7.   
  8.             // high value may be configured by property  
  9.             int h = 127;  
  10.             if (integerCacheHighPropValue != null) {  
  11.                 // Use Long.decode here to avoid invoking methods that  
  12.                 // require Integer's autoboxing cache to be initialized  
  13.                 int i = Long.decode(integerCacheHighPropValue).intValue();  
  14.                 i = Math.max(i, 127);  
  15.                 // Maximum array size is Integer.MAX_VALUE  
  16.                 h = Math.min(i, Integer.MAX_VALUE - -low);  
  17.             }  
  18.             high = h;  
  19.   
  20.             cache = new Integer[(high - low) + 1];  
  21.             int j = low;  
  22.             for(int k = 0; k < cache.length; k++)  
  23.                 cache[k] = new Integer(j++);  
  24.         }  
  25.   
  26.         private IntegerCache() {}  
  27.     }  

          我們可以清楚地看到,IntegerCache有靜態成員變量cache,爲一個擁有256個元素的數組。在IntegerCache中也對cache進行了初始化,即第i個元素是值爲i-128的Integer對象。而-128至127是最常用的Integer對象,這樣的做法也在很大程度上提高了性能。也正因爲如此,“Integeri1=100;Integer i2=100;”,i1與i2得到是相同的對象。

          對比擴展中的第二個實驗,我們得知,當封裝類與基礎類型進行==運行時,封裝類會進行拆箱,拆箱結果與基礎類型對比值;而兩個封裝類進行==運行時,與其它的對象進行==運行一樣,對比兩個對象的地址,也即判斷是否兩個引用是否指向同一個對象。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章