1、什麼是自動裝箱拆箱
很簡單,下面兩句代碼就能夠看到裝箱和拆箱過程java
1 //自動裝箱 2 Integer total = 99; 3 4 //自動拆箱 5 int totalprim = total;
簡單一點說,裝箱就是自動將基本數據類型轉換爲包裝器類型;拆箱就是自動將包裝器類型轉換爲基本數據類型。數組
下面咱們來看看須要裝箱拆箱的類型有哪些:ide
這個過程是自動執行的,那麼咱們須要看看它的執行過程:函數
1 public class Main { 2 public static void main(String[] args) { 3 //自動裝箱 4 Integer total = 99; 5 6 //自定拆箱 7 int totalprim = total; 8 } 9 }
反編譯class文件以後獲得以下內容:性能
1 javap -c StringTest 測試
Integer total = 99;
執行上面那句代碼的時候,系統爲咱們執行了:
Integer total = Integer.valueOf(99);this
int totalprim = total;
執行上面那句代碼的時候,系統爲咱們執行了:
int totalprim = total.intValue();spa
咱們如今就以Integer爲例,來分析一下它的源碼:
一、首先來看看Integer.valueOf函數指針
1 public static Integer valueOf(int i) { 2 return i >= 128 || i < -128 ? new Integer(i) : SMALL_VALUES[i + 128]; 3 }
它會首先判斷i的大小:若是i小於-128或者大於等於128,就建立一個Integer對象,不然執行SMALL_VALUES[i + 128]。code
首先咱們來看看Integer的構造函數:
1 private final int value; 2 3 public Integer(int value) { 4 this.value = value; 5 } 6 7 public Integer(String string) throws NumberFormatException { 8 this(parseInt(string)); 9 }
它裏面定義了一個value變量,建立一個Integer對象,就會給這個變量初始化。第二個傳入的是一個String變量,它會先把它轉換成一個int值,而後進行初始化。
下面看看SMALL_VALUES[i + 128]是什麼東西:
1 private static final Integer[] SMALL_VALUES = new Integer[256];
它是一個靜態的Integer數組對象,也就是說最終valueOf返回的都是一個Integer對象。
因此咱們這裏能夠總結一點:裝箱的過程會建立對應的對象,這個會消耗內存,因此裝箱的過程會增長內存的消耗,影響性能。
二、接着看看intValue函數
1 @Override 2 public int intValue() { 3 return value; 4 }
這個很簡單,直接返回value值便可。
2、相關問題
上面咱們看到在Integer的構造函數中,它分兩種狀況:
一、i >= 128 || i < -128 =====> new Integer(i)
二、i < 128 && i >= -128 =====> SMALL_VALUES[i + 128]
1 private static final Integer[] SMALL_VALUES = new Integer[256];
SMALL_VALUES原本已經被建立好,也就是說在i >= 128 || i < -128是會建立不一樣的對象,在i < 128 && i >= -128會根據i的值返回已經建立好的指定的對象。
說這些可能還不是很明白,下面咱們來舉個例子吧:
1 public class Main { 2 public static void main(String[] args) { 3 4 Integer i1 = 100; 5 Integer i2 = 100; 6 Integer i3 = 200; 7 Integer i4 = 200; 8 9 System.out.println(i1==i2); //true 10 System.out.println(i3==i4); //false 11 } 12 }
代碼的後面,咱們能夠看到它們的執行結果是不同的,爲何,在看看咱們上面的說明。
一、i1和i2會進行自動裝箱,執行了valueOf函數,它們的值在(-128,128]這個範圍內,它們會拿到SMALL_VALUES數組裏面的同一個對象SMALL_VALUES[228],它們引用到了同一個Integer對象,因此它們確定是相等的。
二、i3和i4也會進行自動裝箱,執行了valueOf函數,它們的值大於128,因此會執行new Integer(200),也就是說它們會分別建立兩個不一樣的對象,因此它們確定不等。
下面咱們來看看另一個例子:
1 public class Main { 2 public static void main(String[] args) { 3 4 Double i1 = 100.0; 5 Double i2 = 100.0; 6 Double i3 = 200.0; 7 Double i4 = 200.0; 8 9 System.out.println(i1==i2); //false 10 System.out.println(i3==i4); //false 11 } 12 }
看看上面的執行結果,跟Integer不同,這樣也沒必要奇怪,由於它們的valueOf實現不同,結果確定不同,那爲何它們不統一一下呢?
這個很好理解,由於對於Integer,在(-128,128]之間只有固定的256個值,因此爲了不屢次建立對象,咱們事先就建立好一個大小爲256的Integer數組SMALL_VALUES,因此若是值在這個範圍內,就能夠直接返回咱們事先建立好的對象就能夠了。
可是對於Double類型來講,咱們就不能這樣作,由於它在這個範圍內個數是無限的。
總結一句就是:在某個範圍內的整型數值的個數是有限的,而浮點數卻不是。
因此在Double裏面的作法很直接,就是直接建立一個對象,因此每次建立的對象都不同。
1 public static Double valueOf(double d) { 2 return new Double(d); 3 }
下面咱們進行一個歸類:
Integer派別:Integer、Short、Byte、Character、Long這幾個類的valueOf方法的實現是相似的。
Double派別:Double、Float的valueOf方法的實現是相似的。每次都返回不一樣的對象。
下面對Integer派別進行一個總結,以下圖:
下面咱們來看看另一種狀況:
1 public class Main { 2 public static void main(String[] args) { 3 4 Boolean i1 = false; 5 Boolean i2 = false; 6 Boolean i3 = true; 7 Boolean i4 = true; 8 9 System.out.println(i1==i2);//true 10 System.out.println(i3==i4);//true 11 } 12 }
能夠看到返回的都是true,也就是它們執行valueOf返回的都是相同的對象。
1 public static Boolean valueOf(boolean b) { 2 return b ? Boolean.TRUE : Boolean.FALSE; 3 }
能夠看到它並無建立對象,由於在內部已經提早建立好兩個對象,由於它只有兩種狀況,這樣也是爲了不重複建立太多的對象。
1 public static final Boolean TRUE = new Boolean(true); 2 3 public static final Boolean FALSE = new Boolean(false);
上面把幾種狀況都介紹到了,下面來進一步討論其餘狀況。
1 Integer num1 = 400; 2 int num2 = 400; 3 System.out.println(num1 == num2); //true
說明num1 == num2進行了拆箱操做
1 Integer num1 = 100; 2 int num2 = 100; 3 System.out.println(num1.equals(num2)); //true
咱們先來看看equals源碼:
1 @Override 2 public boolean equals(Object o) { 3 return (o instanceof Integer) && (((Integer) o).value == value); 4 }
咱們指定equal比較的是內容自己,而且咱們也能夠看到equal的參數是一個Object對象,咱們傳入的是一個int類型,因此首先會進行裝箱,而後比較,之因此返回true,是因爲它比較的是對象裏面的value值。
1 Integer num1 = 100; 2 int num2 = 100; 3 Long num3 = 200l; 4 System.out.println(num1 + num2); //200 5 System.out.println(num3 == (num1 + num2)); //true 6 System.out.println(num3.equals(num1 + num2)); //false
一、當一個基礎數據類型與封裝類進行==、+、-、*、/運算時,會將封裝類進行拆箱,對基礎數據類型進行運算。
二、對於num3.equals(num1 + num2)爲false的緣由很簡單,咱們仍是根據代碼實現來講明:
1 @Override 2 public boolean equals(Object o) { 3 return (o instanceof Long) && (((Long) o).value == value); 4 }
它必須知足兩個條件才爲true:
一、類型相同
二、內容相同
上面返回false的緣由就是類型不一樣。
1 Integer num1 = 100; 2 Ingeger num2 = 200; 3 Long num3 = 300l; 4 System.out.println(num3 == (num1 + num2)); //true
咱們來反編譯一些這個class文件:javap -c StringTest
能夠看到運算的時候首先對num3進行拆箱(執行num3的longValue獲得基礎類型爲long的值300),而後對num1和mum2進行拆箱(分別執行了num1和num2的intValue獲得基礎類型爲int的值100和200),而後進行相關的基礎運算。
咱們來對基礎類型進行一個測試:
1 int num1 = 100; 2 int num2 = 200; 3 long mum3 = 300; 4 System.out.println(num3 == (num1 + num2)); //true
就說明了爲何最上面會返回true.
因此,當 「==」運算符的兩個操做數都是 包裝器類型的引用,則是比較指向的是不是同一個對象,而若是其中有一個操做數是表達式(即包含算術運算)則比較的是數值(即會觸發自動拆箱的過程)。
陷阱1:
1 Integer integer100=null; 2 int int100=integer100;
這兩行代碼是徹底合法的,徹底可以經過編譯的,可是在運行時,就會拋出空指針異常。其中,integer100爲Integer類型的對象,它固然能夠指向null。但在第二行時,就會對integer100進行拆箱,也就是對一個null對象執行intValue()方法,固然會拋出空指針異常。因此,有拆箱操做時必定要特別注意封裝類對象是否爲null。
總結:
一、須要知道何時會引起裝箱和拆箱
二、裝箱操做會建立對象,頻繁的裝箱操做會消耗許多內存,影響性能,因此能夠避免裝箱的時候應該儘可能避免。
三、equals(Object o) 由於原equals方法中的參數類型是封裝類型,所傳入的參數類型(a)是原始數據類型,因此會自動對其裝箱,反之,會對其進行拆箱
四、當兩種不一樣類型用==比較時,包裝器類的須要拆箱, 當同種類型用==比較時,會自動拆箱或者裝箱