咱們知道,Java中包含了8種基本數據類型:java
這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。
有以下代碼,你知道輸出結果是什麼嗎?
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自動裝箱後就不相等,這是爲何呢?
一塊兒來找下這個問題的答案:
Integer a = 1
,根據前文咱們知道,這裏發生了自動裝箱,而自動裝箱其實就是調用了Integer
的valueOf
方法valueOf
方法,那咱們是否是應該去看看這個方法裏到底作了什麼事情?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); }
方法實現很簡單,先解釋一下,IntegerCache
是Integer
類中定義的一個private static
的內部類,它維護了一個Integer
數組cache
,IntegerCache
源碼以下:
/** * 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() { } }
從上面源碼能夠看出:
咱們終於找到上面問題的答案了:
Integer類初始化時,會把一個-128~127之間的Integer類型對象放入一個名爲cache的數組中緩存起來。若是之後把一個-128~127之間的基本數據類型自動裝箱成一個Integer實例時(即調用valueOf方法),其實是直接引用了cache數組中的對應元素。但每次把一個不在-128~127範圍內的整數自動裝箱成Integer實例時,就須要從新new
一個Integet實例,因此出現了上面那樣的運行結果。
緩存是一種很是優秀的設計模式,在Java、JavaEE平臺的不少地方都會經過緩存來提升系統的性能。
類型的,Byte、Short、Long、Character也有相同的緩存機制,值得注意的是Double、Float是沒有緩存機制的。有興趣的同窗,能夠自行查看源碼。
null
,不然自動拆箱時就有可能拋出NPE。==
,雖然-128到127之間的數字能夠,可是這個範圍以外仍是須要使用equals
比較。