可能你不知道的,關於自動裝箱和自動拆箱

包裝類

咱們知道,Java中包含了8種基本數據類型:java

  • 整數類型:byte、short、int、long
  • 字符類型:char
  • 浮點類型:float、double
  • 布爾類型:boolean

這8種基本數據類型的變量不須要使用new來建立,它們不會在堆上建立,而是直接在棧內存中存儲,所以會比使用對象更加高效。設計模式

可是,在某些時候,基本數據類型會有一些制約,例如當有個方法須要Object類型的參數,但實際須要的值倒是二、3等數值,這就比較難以處理了。由於,全部引用類型的變量都繼承了Object類,均可當成Object類型變量使用,但基本數據類型的變量就不能夠了。數組

爲了解決這個問題,Java爲這8種基本數據類型分別定義了相應的引用類型,並稱之爲基本數據類型的包裝類(Wrapper Class)。包裝類均位於java.lang包下,其和基本數據類型的對應關係以下表所示:緩存

基本數據類型 包裝類
byte Byte
short Short
int Integer
long Long
char Character
float Float
double Double
boolean Boolean

從上表能夠看出,除了int和char有點例外以外,其餘的基本數據類型對應的包裝類都是將其首字母大寫。app

自動裝箱和自動拆箱

在Java SE5以前,把基本數據類型變量變成包裝類實例須要經過對應包裝類的構造器來實現,即:ide

Integer i = new Integer(10);

把包裝器類型轉換爲基本數據類型須要這樣:性能

int a = i.intValue();

上面的基本數據類型與包裝類對象之間的轉換有點繁瑣,因此從Java SE5開始,爲了簡化開發,Java提供了自動裝箱(Autoboxing)和自動拆箱(AutoUnboxing)功能。ui

所謂自動裝箱,就是自動將基本數據類型轉換爲包裝器類型;自動拆箱,就是自動將包裝器類型轉換爲基本數據類型,下面代碼演示自動裝箱、拆箱。設計

// 自動裝箱
Integer i = 10;
// 自動拆箱
int a = i;

咱們能夠看到,當JDK提供了自動裝箱和自動拆箱功能後,大大簡化了咱們的開發。code

須要注意的是,進行自動裝箱和自動拆箱時必須注意類型匹配。例如,Integer只能自動拆箱成int,int只能自動裝箱成Integer。

原理

經過前文,咱們已經知道了什麼是包裝類及什麼是自動裝、拆箱。

那麼接下來,咱們來看一下自動拆、裝箱的原理。有以下代碼:

public static void main(String[] args) {
    Integer i = 10;// 自動裝箱
    int a = i;// 自動拆箱
}

對以上代碼進行反編譯,獲得以下代碼:

public static void main(String[] args) {
    Integer i = Integer.valueOf(10); 
    int a = integer.intValue(); 
}

從反編譯後獲得的代碼能夠看出,在裝箱的時候自動調用的是Integer的valueOf(int i)方法,而在拆箱的時候自動調用的是Integer的intValue()方法。

其餘的也相似,好比Double、Character。感興趣的同窗,能夠本身嘗試一下。

所以能夠用一句話總結裝箱和拆箱的實現過程:

自動裝箱,都是經過包裝類的valueOf()方法來實現的。

自動拆箱,都是經過包裝類對象的xxxValue()來實現的。

使用場景

咱們瞭解過原理以後,在來看一下,什麼狀況下,Java會幫咱們進行自動拆裝箱。前面提到的變量的初始化和賦值的場景就不介紹了,那是最簡單的也最容易理解的。

咱們主要來看一下,那些可能被忽略的場景

場景一、將基本數據類型放入集合類
List<Integer> list = new ArrayList<>();
list.add(1);

當咱們把基本數據類型放入集合類中的時候,代碼沒有報錯,很明顯,這裏發生了自動裝箱。

將上面代碼進行反編譯,也印證了這一點:

List<Integer> list = new ArrayList<>();
list.add(Integer.valueOf(1));
場景二、包裝類型和基本類型的大小比較
Integer i = 10;
// 輸出true
System.out.println("10的包裝類實例是否大於8?" + (i > 8));

反編譯上面代碼:

Integer i = Integer.valueOf(10);
System.out.println("10的包裝類實例是否大於8?" + (i.intValue() > 8));

能夠看到,當包裝類與基本數據類型進行比較運算時,是先將包裝類進行拆箱成基本數據類型,而後進行比較的。

場景三、包裝類型的運算
Integer i = 10;
Integer j = 20;
// 輸出30
System.out.println(i + j);

反編譯上面代碼:

Integer i = Integer.valueOf(10);
Integer j = Integer.valueOf(20);
System.out.println(i.intValue() + j.intValue());

能夠看到,兩個包裝類型之間的運算,會被自動拆箱成基本類型進行。

場景四、三目運算符的使用
boolean flag = true;
Integer i = 0;
int j = 1;
int k = flag ? i : j;

不少人不知道,其實在第四行,會發生自動拆箱,反編譯後代碼以下:

boolean flag = true;
Integer i = Integer.valueOf(0);
int j = 1;
int k = flag ? i.intValue() : j;

這實際上是三目運算符的語法規範:當第二,第三位操做數分別爲基本類型和對象時,其中的對象就會拆箱爲基本類型進行操做。若是這個時候i的值爲null,那麼就會發生NPE,這一點,是咱們平常開發過程當中的大坑。

觀察如下代碼:

public static void main(String[] args) {
    Map<String, Boolean> map = new HashMap<>();
    Boolean b = map != null ? map.get("test") : false;
    System.out.println(b);
}

通常狀況下,咱們會認爲以上代碼Boolean b的最終獲得的值應該是null。由於map.get("test")的值是null,而b又是一個對象,因此獲得結果會是null

可是,以上代碼會拋出NPE:

Exception in thread "main" java.lang.NullPointerException

這是由於,map.get("test") == null,當用null去調用intValue()方法時,拋出了NPE。

Integer的緩存機制

有以下代碼,你知道輸出結果是什麼嗎?

public static void main(String[] args) {
    Integer a = 1;
    Integer b = 1;
    Integer c = 128;
    Integer d = 128;
    System.out.println(a == b);
    System.out.println(c == d);
}

咱們都知道在Java裏,當用==來比較兩個對象時,比較的是地址,若是兩個對象引用指向堆中的同一塊內存就返回true,不然返回false。這一點是徹底正確的。

那按照這個理論,上面代碼應該輸出都是false,由於4個變量都是Integer類型的對象,但實際輸出結果倒是這樣的:

true
false

這讓人疑惑:一樣是兩個int類型的數值自動裝箱成Integer對象,若是是兩個2自動裝箱後就相等;但若是是兩個128自動裝箱後就不相等,這是爲何呢?

一塊兒來找下這個問題的答案:

  1. Integer a = 1,根據前文咱們知道,這裏發生了自動裝箱,而自動裝箱其實就是調用了IntegervalueOf方法
  2. 既然調用了這個valueOf方法,那咱們是否是應該去看看這個方法裏到底作了什麼事情?
  3. 最後去查看Integer這個包裝類的valueOf的具體實現。

來,一塊兒來看JDK裏valueOf方法的源代碼:

public static Integer valueOf(int i) {
    if (i >= IntegerCache.low && i <= IntegerCache.high)
        return IntegerCache.cache[i + (-IntegerCache.low)];
    return new Integer(i);
}

方法實現很簡單,先解釋一下,IntegerCacheInteger類中定義的一個private static的內部類,它維護了一個Integer數組cacheIntegerCache源碼以下:

/**
     * Cache to support the object identity semantics of autoboxing for values 
     * between -128 and 127 (inclusive) as required by JLS. 
     * <p>
     * The cache is initialized on first usage.  The size of the cache
     * may be controlled by the {@code -XX:AutoBoxCacheMax=<size>} option.
     * During VM initialization, java.lang.Integer.IntegerCache.high property
     * may be set and saved in the private system properties in the
     * sun.misc.VM class.
     */
    private static class IntegerCache {
        static final int low = -128;
        static final int high;
        static final Integer cache[];

        static {
            // high value may be configured by property
            int h = 127;
            String integerCacheHighPropValue =
                    sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
            if (integerCacheHighPropValue != null) {
                try {
                    int i = parseInt(integerCacheHighPropValue);
                    i = Math.max(i, 127);
                    // Maximum array size is Integer.MAX_VALUE
                    h = Math.min(i, Integer.MAX_VALUE - (-low) - 1);
                } catch (NumberFormatException nfe) {
                    // If the property cannot be parsed into an int, ignore it.
                }
            }
            high = h;

            cache = new Integer[(high - low) + 1];
            int j = low;
            for (int k = 0; k < cache.length; k++)
                cache[k] = new Integer(j++);

            // range [-128, 127] must be interned (JLS7 5.1.7)
            assert Integer.IntegerCache.high >= 127;
        }

        private IntegerCache() {
        }
    }

從上面源碼能夠看出:

  • IntegerCache類有一個int類型的常量low,值爲-128
  • 還有一個int類型的常量high,它的值經過靜態初始化塊進行賦值,默認爲127(從javadoc能夠看到,這個值能夠經過虛擬機參數:-XX:AutoBoxCacheMax=<size>進行設置)
  • 最後,還有一個Integer類型的數組cache[],它一樣經過靜態初始化塊進行初始化,長度==(high - low) + 1,默認爲(127+128+1)=256,數組元素值爲-128~127。
  • ps:大佬們寫的代碼真的是太優雅了!!

咱們終於找到上面問題的答案了:

Integer類初始化時,會把一個-128~127之間的Integer類型對象放入一個名爲cache的數組中緩存起來。若是之後把一個-128~127之間的基本數據類型自動裝箱成一個Integer實例時(即調用valueOf方法),其實是直接引用了cache數組中的對應元素。但每次把一個不在-128~127範圍內的整數自動裝箱成Integer實例時,就須要從新new一個Integet實例,因此出現了上面那樣的運行結果。

緩存是一種很是優秀的設計模式,在Java、JavaEE平臺的不少地方都會經過緩存來提升系統的性能。

類型的,Byte、Short、Long、Character也有相同的緩存機制,值得注意的是Double、Float是沒有緩存機制的。有興趣的同窗,能夠自行查看源碼。

總結

  1. 裝箱操做會建立對象,頻繁的裝箱操做會消耗許多內存,影響性能,因此能夠避免裝箱的時候應該儘可能避免。
  2. 有些場景會進行自動拆裝箱,此時要注意包裝類對象是否爲null,不然自動拆箱時就有可能拋出NPE。
  3. 包裝對象的數值比較,不能簡單的使用==,雖然-128到127之間的數字能夠,可是這個範圍以外仍是須要使用equals比較。


相關文章
相關標籤/搜索