自動裝箱與拆箱(轉載)

你還在為開發中頻繁切換環境打包而煩惱嗎?快來試試 Environment Switcher 吧!使用它可以在app運行時一鍵切換環境,而且還支持其他貼心小功能,有了它媽媽再也不用擔心頻繁環境切換了。https://github.com/CodeXiaoMai/EnvironmentSwitcher

自動裝箱和拆箱從 Java 1.5 開始引入,目的是將原始類型值自動地轉換成對應的對象。自動裝箱與拆箱的機制可以讓我們在 Java 的變量賦值或者是方法調用等情況下使用原始類型或者對象類型更加簡單直接。

如果你在 Java1.5 下進行過編程的話,你一定不會陌生這一點,你不能直接地向集合(Collections)中放入原始類型值,因為集合只接收對象。通常這種情況下你的做法是,將這些原始類型的值轉換成對象,然后將這些轉換的對象放入集合中。使用 Integer、Double、Boolean 等這些類我們可以將原始類型值轉換成對應的對象,但是從某些程度可能使得代碼不是那么簡潔精煉。為了讓代碼簡練,Java 1.5 引入了具有在原始類型和對象類型自動轉換的裝箱和拆箱機制。但是自動裝箱和拆箱并非完美,在使用時需要有一些注意事項,如果沒有搞明白自動裝箱和拆箱,可能會引起難以察覺的 bug。

本文將介紹,什么是自動裝箱和拆箱,自動裝箱和拆箱發生在什么時候,以及要注意的事項。

什么是自動裝箱和拆箱

自動裝箱就是 Java 自動將原始類型值轉換成對應的對象,比如將 int 類型的變量轉換成 Integer 對象,這個過程叫做裝箱,反之將 Integer 對象轉換成 int 類型值,這個過程叫做拆箱。因為這里的裝箱和拆箱是自動進行的非人為轉換,所以就稱作為自動裝箱和拆箱。原始類型 byte,short,char,int,long,float,double 和boolean 對應的封裝類為 Byte、Short、Character、Integer、Long、Float、Double、Boolean。

自動裝箱拆箱要點

  • 自動裝箱時編譯器調用 valueOf() 將原始類型值轉換成對象,同時自動拆箱時,編譯器通過調用類似 intValue()、doubleValue() 這類的方法將對象轉換成原始類型值。
  • 自動裝箱是將 boolean 值轉換成 Boolean 對象,byte 值轉換成 Byte 對象,char 轉換成 Character 對象,float 值轉換成 Float 對象,int 轉換成 Integer 對象,long 轉換成 Long 對象,short 轉換成 Short 對象,自動拆箱則是相反的操作。

何時發生自動裝箱和拆箱

自動裝箱和拆箱在 Java 中很常見,比如我們有一個方法,接受一個對象類型的參數,如果我們傳遞一個原始類型值,那么 Java 會自動將這個原始類型值轉換成與之對應的對象。最經典的一個場景就是當我們向 ArrayList 這樣的容器中增加原始類型數據時或者是創建一個參數化的類,比如下面的 ThreadLocal。

ArrayList<Integer> intList = new ArrayList<Integer>();
intList.add(1); //autoboxing - primitive to object
intList.add(2); //autoboxing

ThreadLocal<Integer> intLocal = new ThreadLocal<Integer>();
intLocal.set(4); //autoboxing

int number = intList.get(0); // unboxing
int local = intLocal.get(); // unboxing in Java

舉例說明

上面的部分我們介紹了自動裝箱和拆箱以及它們何時發生,我們知道了自動裝箱主要發生在兩種情況,一種是賦值時,另一種是在方法調用的時候。為了更好地理解這兩種情況,我們舉例進行說明。

賦值時

這是最常見的一種情況,在 Java 1.5 以前我們需要手動地進行轉換才行,而現在所有的轉換都是由編譯器來完成。

//before autoboxing
Integer iObject = Integer.valueOf(3);
int iPrimitive = iObject.intValue()

//after java5
Integer iObject = 3; //autobxing - primitive to wrapper conversion
int iPrimitive = iObject; //unboxing - object to primitive conversion

方法調用時

這是另一個常用的情況,當我們在方法調用時,我們可以傳入原始數據值或者對象,同樣編譯器會幫我們進行轉換。

public static Integer show(Integer iParam){
   System.out.println("autoboxing example - method invocation i: " + iParam);
   return iParam;
}

//autoboxing and unboxing in method invocation
show(3); //autoboxing
int result = show(3); //unboxing because return type of method is Integer

show() 方法接受 Integer 對象作為參數,當調用 show(3) 時,會將 int 值轉換成對應的 Integer 對象,這就是所謂的自動裝箱,show() 方法返回 Integer 對象,而 int result = show(3); 中 result 為 int 類型,所以這時候發生自動拆箱操作,將 show() 方法返回的 Integer 對象轉換成 int 值。

自動裝箱的弊端

自動裝箱有一個問題,那就是在一個循環中進行自動裝箱操作的情況,如下面的例子就會創建多余的對象,影響程序的性能。

Integer sum = 0;
for(int i=1000; i<5000; i++){
   sum += i;
}

上面的代碼 sum += i 可以看成 sum = sum + i,但是 + 這個操作符不適用于Integer 對象,首先 sum 進行自動拆箱操作,然后進行數值相加操作,最后發生自動裝箱操作轉換成 Integer 對象。其內部變化如下:

int result = sum.intValue() + i;
Integer sum = new Integer(result);

由于我們這里聲明的 sum 為 Integer 類型,在上面的循環中會創建將近 4000 個無用的 Integer 對象,在這樣龐大的循環中,會降低程序的性能并且加重了垃圾回收的工作量。因此在我們編程時,需要注意到這一點,正確地聲明變量類型,避免因為自動裝箱引起的性能問題。

重載與自動裝箱

當重載遇上自動裝箱時,情況會比較復雜,可能會讓人產生困惑。在 Java 1.5 之前,value(int) 和 value(Integer) 是完全不相同的方法,開發者不會因為傳入是 int 還是 Integer 調用哪個方法困惑,但是由于自動裝箱和拆箱的引入,處理重載方法時稍微有點復雜。一個典型的例子就是 ArrayList 的 remove() 方法,它有 remove(index)remove(Object) 兩種重載,我們可能會有一點小小的困惑,其實這種困惑是可以驗證并解開的,通過下面的例子我們可以看到,當出現這種情況時,不會發生自動裝箱操作。

public void test(int num){
    System.out.println("method with primitive argument");
}

public void test(Integer num){
    System.out.println("method with wrapper argument");
}

//calling overloaded method
AutoboxingTest autoTest = new AutoboxingTest();
int value = 3;
autoTest.test(value); //no autoboxing 
Integer iValue = value;
autoTest.test(iValue); //no autoboxing

Output:
method with primitive argument
method with wrapper argument

要注意的事項

自動裝箱和拆箱可以使代碼變得簡潔,但是其也存在一些問題和極端情況下的問題,以下幾點需要我們加強注意。

對象相等比較

這是一個比較容易出錯的地方,== 可以用于原始值進行比較,也可以用于對象進行比較,當用于對象與對象之間比較時,比較的不是對象代表的值,而是檢查兩個對象是否是同一對象,這個比較過程中沒有自動裝箱發生。進行值比較不應該使用 ==,而應該使用對象對應的 equals() 方法。看一個能說明問題的例子。

public class AutoboxingTest {

    public static void main(String args[]) {

        // Example 1: == comparison pure primitive – no autoboxing
        int i1 = 1;
        int i2 = 1;
        System.out.println("i1==i2 : " + (i1 == i2)); // true

        // Example 2: equality operator mixing object and primitive
        Integer num1 = 1; // autoboxing
        int num2 = 1;
        System.out.println("num1 == num2 : " + (num1 == num2)); // true

        // Example 3: special case - arises due to autoboxing in Java
        Integer obj1 = 1; // autoboxing will call Integer.valueOf()
        Integer obj2 = 1; // same call to Integer.valueOf() will return same cached Object

        System.out.println("obj1 == obj2 : " + (obj1 == obj2)); // true

        // Example 4: equality operator - pure object comparison
        Integer one = new Integer(1); // no autoboxing
        Integer anotherOne = new Integer(1);
        System.out.println("one == anotherOne : " + (one == anotherOne)); // false
    }
}

Output:
i1==i2 : true
num1 == num2 : true
obj1 == obj2 : true
one == anotherOne : false

值得注意的是第三個小例子,這是一種極端情況。obj1 和 obj2 的初始化都發生了自動裝箱操作。但是出于節省內存的考慮,JVM 會緩存 -128 到 127 的 Integer
對象。因為 obj1 和 obj2 實際上是同一個對象,所以使用 == 比較返回 true。

容易混亂的對象和原始數據值

另一個需要避免的問題就是混亂使用對象和原始數據值,一個具體的例子就是當我們在對一個原始數據值和一個對象進行比較時,如果這個對象沒有進行初始化或者為Null,在自動拆箱過程中 obj.xxxValue() 會拋出 NullPointerException,如下面的代碼:

private static Integer count;

//NullPointerException on unboxing
if( count <= 0){
  System.out.println("Count is not started yet");
}

緩存的對象

這個問題就是我們上面提到的極端情況,在 Java 中,會對 -128 到 127 的
Integer 對象進行緩存,當創建新的 Integer 對象時,如果符合這個這個范圍,并且已有存在的相同值的對象,則返回這個對象,否則創建新的 Integer 對象。

在 Java 中另一個節省內存的例子就是字符串常量池

生成無用對象增加GC壓力

因為自動裝箱會隱式地創建對象,像前面提到的那樣,如果在一個循環體中,會創建無用的中間對象,這樣會增加 GC 壓力,降低程序的性能。所以在寫循環時一定要注意,避免引入不必要的自動裝箱操作。

如想了解垃圾回收和內存優化,可以查看Google IO:Android內存管理主題演講記錄

總的來說,自動裝箱和拆箱著實為開發者帶來了很大的方便,但是在使用時也是需要格外留意,避免引起出現文章提到的問題。

原文信息

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • 自動裝箱和拆箱從Java1.5開始引入,目的是將原始類型值轉自動地轉換成對應的對象。自動裝箱與拆箱的機制可以讓我們...
    GB_speak閱讀 624評論 0 4
  • 自動裝箱和拆箱從Java 1.5開始引入,目的是將原始類型值轉自動地轉換成對應的對象。自動裝箱與拆箱的機制可以讓我...
    codersm閱讀 423評論 0 0
  • 深入理解Java中的包裝類與自動拆裝箱 文章出處:安卓進階學習指南 作者:麥田哥(Whea...
    wheat7閱讀 2,698評論 2 11
  • 朋友圈里,舍友小A發了一條狀態。鏡頭里,她站在大大的風車下面,巧笑倩兮。下面的地址顯示為荷蘭.阿姆斯特丹。 嚴格來...
    施師閱讀 353評論 0 0