你真的瞭解裝箱和拆箱嗎?

什麼是裝箱和拆箱

在一名 C++ 程序員看來,Java 中不少名詞聽着高大上,實則很是簡單,裝箱和拆箱尤甚。來看下裝箱和拆箱的定義:java

裝箱是指將基本類型轉化成包裝器類型,拆箱是指將包裝器類型轉化成基本數據類型。程序員

Java 有八種基本數據類型 byte/char/boolean/short/int/long/float/double 。在一切皆對象的 Java 世界裏,固然也少不了它們的對象包裝類型 Byte/Character/Boolean/Short/Integer/Long/Float/Double。用一張圖表達它們的關係最簡單不過:數組

從代碼層面看,裝箱與拆箱演示示例代碼 1 以下:

public class BoxTest {
    public static void main(String[] args) {
        Long objectLong100 = 100L;
        long baseLong100 = objectLong100;
    }
}
複製代碼

其中Long objectLong100 = 100L;對 100L 進行裝箱成 Long 類型並賦給 objectLong100 對象類型變量,同理long baseLong100 = objectLong100;對objectLong100 進行了拆箱並賦值給基本類型變量 baseLong100。緩存

自動裝箱和拆箱

什麼是自動裝箱和拆箱

從示例代碼 1 能夠看到基本類型和包裝類型之間的轉換可自動進行。用 javap 反編譯上述代碼可看到以下字節碼: post

從字節碼能夠看出,示例代碼 1 被編譯器改寫成以下形式:spa

public class BoxTest {
    public static void main(String[] args) {
        Long objectLong100 = Long.valueOf(100L);
        long baseLong100 = objectLong100.longValue();
    }
}
複製代碼

這就是 Java 語法糖之一的(Java 語法糖還有不少,可參考《Hollis原創|不瞭解這12個語法糖,別說你會Java》)---自動裝箱和拆箱,它省去了程序員顯示的用代碼在基本類型和包裝器類型之間進行轉化。code

什麼時候進行自動裝箱和拆箱

自動裝箱的情景比較單一:將基本類型賦值給包裝器類型的時候,這裏的賦值不必定是 =/-=/+= 符號,也能夠是方法調用 都會觸發自動裝箱。以下:cdn

public class BoxTest {

    public static void main(String[] args) {
        //= 賦值自動裝箱
        Long objectLong = 100L;

        //equals 調用自動裝箱
        if (objectLong.equals(100L)) {
            System.out.println("objectLong.equals(100L)");
        }
        
        //add 方法調用自動裝箱
        List<Long> objectLongList = new ArrayList<>();
        objectLongList.add(objectLong);
    }
}
複製代碼

同理,將包裝器類型賦值給基本類型的時候會發生自動拆箱:對象

public class BoxTest {

    public static void unpacking(long var) {
        System.out.println("var = " + var);
    }
    
    public static void main(String[] args) {
        Long objectLong = 100L;

        //= 賦值自動拆箱
        long basicLong = objectLong;

        //方法調用自動拆箱
        unpacking(objectLong);
    }
}
複製代碼

但與自動裝箱不一樣,自動拆箱還會在以下狀況發生:blog

一、當包裝器類型之間或者包裝器類型與基本類型之間進行數學運算(+、-、*、/、++、--等)的時候。

二、當包裝器類型與基本類型之間進行 ==、!= 比較操做時候。

其中 1 很好理解,畢竟數學運算(想一想 CPU 操做)只能在基本類型之間進行而不能在對象之間進行。2 舉個例子來簡單說明:

public class BoxTest {

    public static void main(String[] args) {

        Long objectLong = 500L;
        long basicLong = 500L;

        if (objectLong == basicLong) {
            System.out.println("objectLong == basicLong");
        } else {
            System.out.println("objectLong != basicLong");
        }
    }
}
複製代碼

執行輸出objectLong == basicLong,這是在基本類型與包裝器類型之間進行比較,發生了拆箱操做,因此輸出相等。若是仍是覺得 basicLong 會被裝箱成包裝器類型再與 objectLong 比較,那就大錯特錯了

自動裝箱和拆箱中的"陷阱"

自動裝箱和拆箱大法好,可是其中也存在不少"陷阱",且聽我抽絲剝繭,一一道來。來看一段代碼:

public class BoxTest {
    public static void main(String[] args) {

        Long objectLong1 = new Long(10);
        Long objectLong2 = new Long(10);

        Long objectLong11 = 10L;
        Long objectLong22 = 10L;

        Long objectLong5 = 1000L;
        Long objectLong6 = 1000L;

        Long objectLong3 = new Long(10);
        long basicLong = 10L;
        
        if(objectLong1 == objectLong2) {
            System.out.println("objectLong1 == objectLong2");
        } else {
            System.out.println("objectLong1 != objectLong2");
        }

        if(objectLong11 == objectLong22) {
            System.out.println("objectLong11 == objectLong22");
        } else {
            System.out.println("objectLong11 != objectLong22");
        }

        if(objectLong5 == objectLong6) {
            System.out.println("objectLong1 == objectLong2");
        } else {
            System.out.println("objectLong1 != objectLong2");
        }

        if (objectLong3 == basicLong) {
            System.out.println("objectLong3 == basicLong");
        } else {
            System.out.println("objectLong3 != basicLong");
        }
    }
}
複製代碼

先用筆寫下你認爲的輸出的結果,再看我下面截圖中的輸出,看你"執行"的對不對。運行結果以下:

一、objectLong1 與 objectLong2 的比較結果不出意外,由於他們是兩個包裝器類型變量。因爲它們並非同一個對象,因此並不相等。

二、objectLong3 與 basicLong 的比較根據什麼時候進行自動裝箱和拆箱講到的拆箱的場景也能夠快速理解。

三、objectLong5 與 objectLong6 比較不相等大部分人還能推理的到:objectLong5 與 objectLong6 都發生自動裝箱成包裝器對象,兩個對象之間的 == 比較並不相等。但 objectLong11 與 objectLong22 的比較輸出結果恐怕要讓不少人大跌眼鏡。 所以咱們來看下 Long 自動裝箱時調用的方法 Long.valueOf(long value) 的源代碼:

public final class Long extends Number implements Comparable<Long> {
    //......此處省略一堆
    private static class LongCache {
        private LongCache(){}

        static final Long cache[] = new Long[-(-128) + 127 + 1];

        static {
            for(int I = 0; I < cache.length; I++)
                cache[I] = new Long(I - 128);
        }
    }
    //......此處省略一堆
    public static Long valueOf(long l) {
        final int offset = 128;
        if (l >= -128 && l <= 127) { // will cache
            return LongCache.cache[(int)l + offset];
        }
        return new Long(l);
    }
    //......此處省略一堆
}
複製代碼

能夠看出當要進行自動裝箱的基本類型值在 [-128~127] 之間的時候,直接將 LongCache 中緩存的對象返回,而 LongCache.cache 是一個數組,裏面保存了值爲 -128~127 的包裝器對象,在 Long.class 第一次加載的時候分配。 這就解釋了爲何 10L 自動裝箱成兩個對象,他們之間是相等的。而 1000L 自動裝箱成兩個對象卻不相等。前者用的是 LongCache.cache 中緩存中的同一個對象,後者每次自動裝箱都 new 一個新對象

其餘包裝器類型在裝箱時都採用了相似的緩存方法,防止生成太多對象,但 Float 和 Double 例外。自動裝箱方法大致上分爲如下三種:

  • 可窮舉的,如 Byte/Character/Boolean,緩存了全部可能值的對象。因此他們全部相同值的包裝器對象之間的比較都是相等的,而大部分比較是不相等的。具備不肯定性。
  • 不可窮舉不連續的,如 Short/Integer/Long,只緩存了範圍內的經常使用值對象,全部他們部分相同值的包裝器對象之間的比較是相等的。
  • 不可枚舉且連續的,如 Float/Double,每次裝箱都是 new 一個新對象。

感興趣的能夠點進包裝類源碼中進行查看,比較簡單。

總結和建議

一、包裝器類型本質上是一個對象,自動裝箱時先在堆中 new 一個對象,再將基本類型的值賦給該對象中的 value。

二、必定不要使用 ==/!= 在包裝器類型之間、包裝器與基本類型之間進行比較。請使用 equals 方法

三、當進行數學運算時,遇到包裝器類型會進行自動拆箱,再對基本類型進行運算

相關文章
相關標籤/搜索