天天看點

java拆箱和裝箱_詳解Java的自動裝箱與拆箱(Autoboxing and unboxing)

一、什麼是自動裝箱拆箱

很簡單,下面兩句代碼就可以看到裝箱和拆箱過程

1 //自動裝箱

2 Integer total = 99;

3

4 //自動拆箱

5 int totalprim = total;

簡單一點說,裝箱就是自動将基本資料類型轉換為包裝器類型;拆箱就是自動将包裝器類型轉換為基本資料類型。

下面我們來看看需要裝箱拆箱的類型有哪些:

java拆箱和裝箱_詳解Java的自動裝箱與拆箱(Autoboxing and unboxing)
java拆箱和裝箱_詳解Java的自動裝箱與拆箱(Autoboxing and unboxing)

這個過程是自動執行的,那麼我們需要看看它的執行過程:

java拆箱和裝箱_詳解Java的自動裝箱與拆箱(Autoboxing and unboxing)

1 public class Main {

2 public static void main(String[] args) {

3 //自動裝箱

4 Integer total = 99;

5

6 //自定拆箱

7 int totalprim = total;

8 }

9 }

java拆箱和裝箱_詳解Java的自動裝箱與拆箱(Autoboxing and unboxing)

反編譯class檔案之後得到如下内容:

1 javap -c StringTest

java拆箱和裝箱_詳解Java的自動裝箱與拆箱(Autoboxing and unboxing)

Integer total = 99;

執行上面那句代碼的時候,系統為我們執行了:

Integer total = Integer.valueOf(99);

int totalprim = total;

執行上面那句代碼的時候,系統為我們執行了:

int totalprim = total.intValue();

我們現在就以Integer為例,來分析一下它的源碼:

1、首先來看看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]。

首先我們來看看Integer的構造函數:

java拆箱和裝箱_詳解Java的自動裝箱與拆箱(Autoboxing and unboxing)

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 }

java拆箱和裝箱_詳解Java的自動裝箱與拆箱(Autoboxing and unboxing)

它裡面定義了一個value變量,建立一個Integer對象,就會給這個變量初始化。第二個傳入的是一個String變量,它會先把它轉換成一個int值,然後進行初始化。

下面看看SMALL_VALUES[i + 128]是什麼東西:

1 private static final Integer[] SMALL_VALUES = new Integer[256];

它是一個靜态的Integer數組對象,也就是說最終valueOf傳回的都是一個Integer對象。

是以我們這裡可以總結一點:裝箱的過程會建立對應的對象,這個會消耗記憶體,是以裝箱的過程會增加記憶體的消耗,影響性能。

2、接着看看intValue函數

1 @Override

2 public int intValue() {

3 return value;

4 }

這個很簡單,直接傳回value值即可。

二、相關問題

上面我們看到在Integer的構造函數中,它分兩種情況:

1、i >= 128 || i < -128 =====> new Integer(i)

2、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的值傳回已經建立好的指定的對象。

說這些可能還不是很明白,下面我們來舉個例子吧:

java拆箱和裝箱_詳解Java的自動裝箱與拆箱(Autoboxing and unboxing)

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 }

java拆箱和裝箱_詳解Java的自動裝箱與拆箱(Autoboxing and unboxing)

代碼的後面,我們可以看到它們的執行結果是不一樣的,為什麼,在看看我們上面的說明。

1、i1和i2會進行自動裝箱,執行了valueOf函數,它們的值在(-128,128]這個範圍内,它們會拿到SMALL_VALUES數組裡面的同一個對象SMALL_VALUES[228],它們引用到了同一個Integer對象,是以它們肯定是相等的。

2、i3和i4也會進行自動裝箱,執行了valueOf函數,它們的值大于128,是以會執行new Integer(200),也就是說它們會分别建立兩個不同的對象,是以它們肯定不等。

下面我們來看看另外一個例子:

java拆箱和裝箱_詳解Java的自動裝箱與拆箱(Autoboxing and unboxing)

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 }

java拆箱和裝箱_詳解Java的自動裝箱與拆箱(Autoboxing and unboxing)

看看上面的執行結果,跟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派别進行一個總結,如下圖:

java拆箱和裝箱_詳解Java的自動裝箱與拆箱(Autoboxing and unboxing)

下面我們來看看另外一種情況:

java拆箱和裝箱_詳解Java的自動裝箱與拆箱(Autoboxing and unboxing)

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 }

java拆箱和裝箱_詳解Java的自動裝箱與拆箱(Autoboxing and unboxing)

可以看到傳回的都是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

1、當一個基礎資料類型與封裝類進行==、+、-、*、/運算時,會将封裝類進行拆箱,對基礎資料類型進行運算。

2、對于num3.equals(num1 + num2)為false的原因很簡單,我們還是根據代碼實作來說明:

1 @Override

2 public boolean equals(Object o) {

3 return (o instanceof Long) && (((Long) o).value == value);

4 }

它必須滿足兩個條件才為true:

1、類型相同

2、内容相同

上面傳回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

java拆箱和裝箱_詳解Java的自動裝箱與拆箱(Autoboxing and unboxing)

可以看到運算的時候首先對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。

總結:

1、需要知道什麼時候會引發裝箱和拆箱

2、裝箱操作會建立對象,頻繁的裝箱操作會消耗許多記憶體,影響性能,是以可以避免裝箱的時候應該盡量避免。

3、equals(Object o) 因為原equals方法中的參數類型是封裝類型,所傳入的參數類型(a)是原始資料類型,是以會自動對其裝箱,反之,會對其進行拆箱

4、當兩種不同類型用==比較時,包裝器類的需要拆箱, 當同種類型用==比較時,會自動拆箱或者裝箱

hello world!!!