天天看點

Java的自動裝箱和拆箱

Java的自動裝箱和拆箱

目錄

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

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

三、需要注意的點

四、其他一些問題

五、總結

自動裝箱自動拆箱是在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 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 類型的比較。

把編譯錯誤的代碼注釋掉,反編譯看一下猜想是否正确。

Code:
   0: aload_0
   1: invokespecial #1          // Method java/lang/Object."<init>":()V
   4: return
           
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