Java的自動裝箱和拆箱

LewisYoung發表於2020-04-24

如需轉載請私信或者表明轉載地址
https://www.cnblogs.com/lewisyoung/p/12769084.html

目錄

一、什麼是自動裝箱自動拆箱

自動裝箱自動拆箱是在JDK5以後引入的一個特性。在學習Java的過程中,我們認識到有八種基礎型別,以及他們對應的包裝型別。

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

簡單的來說,自動裝箱就是講基礎型別轉換成包裝型別,自動拆箱就是講包裝型別轉換成基礎型別。說是自動的,是因為我們沒有在程式碼中顯示的呼叫相關方法,轉換過程由編譯器自動幫我們完成。
比如下面程式碼:

	int i0 = 0;    //建立基礎型別
    Integer i1 = i0;    //自動裝箱
    int i2 = i1;    //自動拆箱

二、自動裝箱自動拆箱的實質

想要知道自動拆箱自動裝箱的實質,我們就得跟著程式碼走一走。這裡我們用 int 和 Integer 型別為例(用的比較多),其他的基本型別和包裝型別大家自己可以試一下。
比如下面這個檔案,SolutionTest.java

public class SolutionTest {
	 public static void main(String[] args) {
		 int i0 = 0;    //建立基礎型別
		 Integer i1 = i0;    //自動裝箱
		 int i2 = i1;    //自動拆箱
	 }
}

我們對其進行編譯和反編譯後,得到的結果

Compiled from "SolutionTest.java"
public class SolutionTest {
  public SolutionTest();
    Code:
       0: aload_0
       1: invokespecial #1          // Method java/lang/Object."<init>":()V
       4: return

  public static void main(java.lang.String[]);
    Code:
       0: iconst_0
       1: istore_1
       2: iload_1
       3: invokestatic  #2          // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
       6: astore_2
       7: aload_2
       8: invokevirtual #3          // Method java/lang/Integer.intValue:()I
      11: istore_3
      12: return
}

我們可以看到在Integer i1 = i0;時,系統執行的是 Integer.valueOf 的方法,在int i2 = i1;時,系統執行了 Integer.intValue 方法。
哪路紅豆,原來自動裝箱和自動拆箱其實就是呼叫了 Integer 類的兩個方法啊!那我們再來看看 Integer 的原始碼:

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

這個方法裡面實現了兩個步驟:
1、首先判斷快取池中有沒有值為 i 的物件,有的話就返回該物件。(ps:IntegerCache是Integer的靜態內部類,cache是一個Integer型別的陣列)
2、如果快取池中不存在值值為 i 的物件,那就建一個並返回唄。

然後是intValue方法:

public int intValue() {
    return value;
}

這個就直接返回了 Integer 的內部成員 value。值得一提的是,Integer 裡面儲存值的其實還是一個 int 型別的 value ,而 value 是用 final 修飾的,就是說你無法改變 Integer 例項的值,你看起來改變值的其實都是返回了另一個例項(這跟 IntegerCache 的快取池有關,思考一下)。
好了,這下就明白為啥了。

三、需要注意的點

注意的點主要分為兩塊:1、實際開發過程中遇到的問題;2、面試可能會問到的問題。
實際開發中的問題主要涉及到的就是 Object 類的 equals 方法和 == 運算子的區別。比如下面程式碼:

	int i0 = 0;    
    Integer i1 = i0;
    int i2 = i1; 
    System.out.println(i0 == i1);    // true
    System.out.println(i1 == i2);    // true
    System.out.println(i0 == i2);    // true
    System.out.println(i0.equals(i1));    // 編譯錯誤
    System.out.println(i1.equals(i2));    // true
    System.out.println(i2.equals(i0));    // 編譯錯誤

除了不能編譯的,其他的都為 true。在反編譯之前,我們先自己結合上面的知識思考一下為什麼呢?
沒錯,因為在使用 == 運算子的時候自動拆箱裝箱了,而 equals 方法程式碼如下:

public boolean equals(Object obj) {
    if (obj instanceof Integer) {
        return value == ((Integer)obj).intValue();
    }
    return false;
}

看的出來本質上還是 int 型別的比較。
把編譯錯誤的程式碼註釋掉,反編譯看一下猜想是否正確。

Compiled from "SolutionTest.java"
public class SolutionTest {
  public SolutionTest();
    Code:
       0: aload_0
       1: invokespecial #1          // Method java/lang/Object."<init>":()V
       4: return

  public static void main(java.lang.String[]);
    Code:
       0: iconst_0
       1: istore_1
       2: iload_1
       3: invokestatic  #2          // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
       6: astore_2
       7: aload_2
       8: invokevirtual #3          // Method java/lang/Integer.intValue:()I
      11: istore_3
      12: getstatic     #4          // Field java/lang/System.out:Ljava/io/PrintStream;
      15: iload_1
      16: aload_2
      17: invokevirtual #3          // Method java/lang/Integer.intValue:()I
      20: if_icmpne     27
      23: iconst_1
      24: goto          28
      27: iconst_0
      28: invokevirtual #5          // Method java/io/PrintStream.println:(Z)V
      31: getstatic     #4          // Field java/lang/System.out:Ljava/io/PrintStream;
      34: aload_2
      35: invokevirtual #3          // Method java/lang/Integer.intValue:()I
      38: iload_3
      39: if_icmpne     46
      42: iconst_1
      43: goto          47
      46: iconst_0
      47: invokevirtual #5          // Method java/io/PrintStream.println:(Z)V
      50: getstatic     #4          // Field java/lang/System.out:Ljava/io/PrintStream;
      53: iload_1
      54: iload_3
      55: if_icmpne     62
      58: iconst_1
      59: goto          63
      62: iconst_0
      63: invokevirtual #5          // Method java/io/PrintStream.println:(Z)V
      66: getstatic     #4          // Field java/lang/System.out:Ljava/io/PrintStream;
      69: aload_2
      70: iload_3
      71: invokestatic  #2          // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
      74: invokevirtual #6          // Method java/lang/Integer.equals:(Ljava/lang/Object;)Z
      77: invokevirtual #5          // Method java/io/PrintStream.println:(Z)V
      80: return
}

從結果看的出來,在 equals 的時候,int 引數先被自動裝箱為 Integer 型別,然後進行比較。在 == 比較時,Integer 型別自動拆箱。

面試中可能碰到的問題,比如說:Integer i1 = 0;這句程式碼經過了什麼流程,快取池什麼時候建立、初始化的大小、什麼時候擴充、擴充多少等問題。這些看原始碼就好啦。
1、Integer i1 = 0;這句程式碼經過了什麼流程?
呼叫valueOf方法 -> 判斷是否在快取池中 ->有?返回快取池中物件:返回新建物件
2、快取池什麼時候建立?
快取池是 Integer 的靜態內部類 IntegerCache 靜態方法中初始化的,那麼也就是說第一次 Integer 類載入的時候就全部載入了。
3、初始化的大小?
預設是 -127 ~ 128。但看程式碼可以知道,這個虛擬機器中的屬性有關:

static {
    // high value may be configured by property
    int h = 127;
    String integerCacheHighPropValue =
        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 IntegerCache.high >= 127;
        }

        private IntegerCache() {}
}

4、什麼時候擴充以及擴充多少?
啊。。。原始碼中沒看到,不太清楚。

四、其他一些問題

1、在使用集合類時,都用 Integer 而不能用 int。
2、下面程式碼能編譯通過但是執行時會報空指標異常:

	Integer i1 = null;
	int i2 = i1;

原因是自動拆箱時對一個 null 物件進行 intValue ,自然會報錯。

五、總結

1、自動裝箱就是講基礎型別轉換成包裝型別,自動拆箱就是講包裝型別轉換成基礎型別。
2、自動拆箱裝箱實際上是呼叫了包裝類的方法。
3、快取池的存在以及使用。
4、== 比較時: 基礎型別之間不用說,基礎型別和包裝型別會進行自動拆箱,包裝型別和包裝型別之間是正常型別的比較。
5、equals 方法,引數為基礎型別時會進行自動裝箱。裝箱後變成取 intValue 後基礎型別值的比較。

如需轉載請私信或者表明轉載地址
https://www.cnblogs.com/lewisyoung/p/12769084.html

相關文章