1. 程式人生 > 其它 >Java包裝類詳解(二)

Java包裝類詳解(二)

技術標籤:Java基礎java面試

往期

Java包裝類詳解(一)

面試相關問題

1. 下面這段程式碼的輸出結果是什麼?

public class Main {
    public static void main(String[] args) {
         
        Integer i1 = 100;
        Integer i2 = 100;
        Integer i3 = 200;
        Integer i4 = 200;
         
        System.out.println(i1==i2);  
        System.
out.println(i3==i4); } }

答案:

true
false

輸出結果表明i1i2指向的是同一個物件,而i3i4指向的是不同的物件。Java 8中IntegervalueOf()方法的具體實現如下:

// IntegerCache.low = -128
// IntegerCache.high 可通過JVM引數 -XX:AutoBoxCacheMax=<size> 設定,必須大於等於127,預設為127
public static Integer valueOf(int i) {
    if (i >= IntegerCache.low &&
i <= IntegerCache.high) return IntegerCache.cache[i + (-IntegerCache.low)]; return new Integer(i); }

其中IntegerCache是一個私有靜態內部類,其實現如下:

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 IntegerCache.high >= 127; } private IntegerCache() {} }

IntegerCache表示Integer快取,其中的cache變數是一個靜態Integer陣列,在靜態初始化塊中被初始化,預設情況下,儲存了-128~127共256個整數對應的Integer物件。

valueOf程式碼中,如果數值位於被快取的範圍,則直接從IntegerCache中獲取已預先建立的Integer物件,只有不在快取範圍時,才通過new建立物件。

通過共享常用物件,可以節省記憶體空間,由於Integer是不可變的,所以快取的物件可以安全的被共享。Boolean、Byte、Short、Long、Character都有類似的實現。這種共享常用物件的思路,叫享元模式,英文叫Flyweight,即共享的輕量級元素。

2. 下面這段程式碼的輸出結果是什麼?

public class Main {
    public static void main(String[] args) {
         
        Double i1 = 100.0;
        Double i2 = 100.0;
        Double i3 = 200.0;
        Double i4 = 200.0;
         
        System.out.println(i1==i2); 
        System.out.println(i3==i4);  
    }
}

答案:

false
false

Java 8中DoublevalueOf()方法的具體實現如下,不難理解答案為何都是false

public static Double valueOf(double d) {
    return new Double(d);
}

為什麼Double類的valueOf()方法會採用與Integer類的valueOf()方法不同的實現。很簡單:在某個範圍內的整型數值的個數是有限的,而浮點數卻不是

需要注意的是,Integer、Short、Byte、Character、Long這幾個類的valueOf()方法的實現是類似的:

public static Byte valueOf(byte b) {
    final int offset = 128;
    return ByteCache.cache[(int)b + offset];
}

public static Long valueOf(long l) {
    final int offset = 128;
    if (l >= -128 && l <= 127) { // will cache
        return LongCache.cache[(int)l + offset];
    }
    return new Long(l);
}

public static Character valueOf(char c) {
    if (c <= 127) { // must cache
        return CharacterCache.cache[(int)c];
    }
    return new Character(c);
}

public static Short valueOf(short s) {
    final int offset = 128;
    int sAsInt = s;
    if (sAsInt >= -128 && sAsInt <= 127) { // must cache
        return ShortCache.cache[sAsInt + offset];
    }
    return new Short(s);
}

Double、FloatvalueOf()方法的實現是類似的:

public static Float valueOf(float f) {
    return new Float(f);
}

3. 下面這段程式碼的輸出結果是什麼?

public class Main {
    public static void main(String[] args) {
         
        Boolean i1 = false;
        Boolean i2 = false;
        Boolean i3 = true;
        Boolean i4 = true;
         
        System.out.println(i1==i2); 
        System.out.println(i3==i4); 
    }
}

答案:

true
true

看完Boolean類的valueOf方法的實現後,答案同樣一目瞭然!

/**
* The {@code Boolean} object corresponding to the primitive
* value {@code true}.
*/
public static final Boolean TRUE = new Boolean(true);

/**
* The {@code Boolean} object corresponding to the primitive
* value {@code false}.
*/
public static final Boolean FALSE = new Boolean(false);

public static Boolean valueOf(boolean b) {
    return (b ? TRUE : FALSE);
}

4. Integer i = new Integer(10)Integer i = 10這兩種方式的區別

  • 第一種方式不會觸發自動裝箱;第二種方式會;
  • 從執行效率和資源佔用上來講,new每次都會建立一個新物件,而除了FloatDouble外的其他包裝類,都會快取包裝類物件,減少需要建立物件的次數,節省空間,提升效能。
  • 從Java 9開始,這些構造方法已經被標記為過時了,推薦使用靜態的valueOf方法。

5. 下面程式的輸出結果是什麼?

public class Main {
    public static void main(String[] args) {         
        Integer a = 1;
        Integer b = 2;
        Integer c = 3;
        Integer d = 3;
        Integer e = 321;
        Integer f = 321;
        Long g = 3L;
        Long h = 2L;         
        System.out.println(c==d);
        System.out.println(e==f);
        System.out.println(c==(a+b));
        System.out.println(c.equals(a+b));
        System.out.println(g==(a+b));
        System.out.println(g.equals(a+b));
        System.out.println(g.equals(a+h));
    }
}

答案:

System.out.println(c==d);               // true
System.out.println(e==f);               // false
System.out.println(c==(a+b));           // true
System.out.println(c.equals(a+b));      // true
System.out.println(g==(a+b));           // true
System.out.println(g.equals(a+b));      // false
System.out.println(g.equals(a+h));      // true
  • c==de == f 的結果不多解釋了。

  • System.out.println(c==(a+b))對應的位元組碼如下:

     91 aload_3         // 將c壓入運算元棧
     92 invokevirtual #10 <java/lang/Integer.intValue>    // 自動拆箱
     95 aload_1         // 將a壓入運算元棧
     96 invokevirtual #10 <java/lang/Integer.intValue>    // 自動拆箱
     99 aload_2         // 將b壓入運算元棧
    100 invokevirtual #10 <java/lang/Integer.intValue>    // 自動拆箱
    103 iadd            // 計算a+b的值並將結果壓入運算元棧
    104 if_icmpne 111 (+7)   // 比較 c 和 a+b 的值
    107 iconst_1
    108 goto 112 (+4)
    111 iconst_0
    112 invokevirtual #9 <java/io/PrintStream.println>
    115 getstatic #8 <java/lang/System.out>
    

    ==運算子的兩個運算元都是包裝器型別的引用時,則比較的是引用地址,而如果其中有一個運算元是表示式(即包含算術運算子)則比較的是數值(即會觸發自動拆箱過程),從對應的位元組碼也可以看出呼叫了intValue()方法,觸發了自動拆箱,比較它們的數值是否相等,因此c==(a+b)的結果為true

  • System.out.println(c.equals(a+b))對應的位元組碼如下:

    118 aload_3     // 將c壓入運算元棧
    119 aload_1     // 將a壓入運算元棧
    120 invokevirtual #10 <java/lang/Integer.intValue> // 自動拆箱
    123 aload_2     // 將b壓入運算元棧
    124 invokevirtual #10 <java/lang/Integer.intValue> // 自動拆箱 
    127 iadd        // 計算a+b的值並將結果壓入運算元棧
    128 invokestatic #2 <java/lang/Integer.valueOf>    // 將a+b的值的自動裝箱
    131 invokevirtual #11 <java/lang/Integer.equals>   // 呼叫equals方法
    134 invokevirtual #9 <java/io/PrintStream.println>
    137 getstatic #8 <java/lang/System.out>
    

    從位元組碼可以看出,c.equals(a+b)首先觸發自動拆箱,又觸發了自動裝箱,再呼叫equals方法,而所有的包裝類都重寫了Object類中的equals方法,equals用於判斷當前物件和引數傳入的物件是否相同,Object類的預設實現是比較地址,它和比較運算子(==)的結果是一樣的。

    equals應該反映的是物件間的邏輯相等關係,所以包裝類都重寫了該實現,實際比較用的是其包裝的基本型別值,對於Integer類,其equals方法程式碼如下(Java 8):

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

    因此,c.equals(a+b)的結果為true.

  • System.out.println(g==(a+b))對應的位元組碼如下:

    140 aload 7       // 將g壓入運算元棧
    142 invokevirtual #12 <java/lang/Long.longValue>    // 自動拆箱
    145 aload_1       // 將a壓入運算元棧 
    146 invokevirtual #10 <java/lang/Integer.intValue>  // 自動拆箱
    149 aload_2       // 將b壓入運算元棧
    150 invokevirtual #10 <java/lang/Integer.intValue>  // 自動拆箱
    153 iadd          // 計算a+b的值並將結果壓入運算元棧
    154 i2l           // 將 a+b 的結果從 int 轉換為 long
    155 lcmp          // 比較 g 和 a+b 的值
    156 ifne 163 (+7) 
    159 iconst_1
    160 goto 164 (+4)
    163 iconst_0
    164 invokevirtual #9 <java/io/PrintStream.println>
    167 getstatic #8 <java/lang/System.out>
    

    g==(a+b)的比較過程和c==(a+b)類似,多了一個型別轉換,都是比較數值,因此結果為true.

  • System.out.println(g.equals(a+b))對應的位元組碼如下:

    170 aload 7        // 將g壓入運算元棧
    172 aload_1        // 將a壓入運算元棧
    173 invokevirtual #10 <java/lang/Integer.intValue>  // 自動拆箱
    176 aload_2        // 將b壓入運算元棧
    177 invokevirtual #10 <java/lang/Integer.intValue>  // 自動拆箱
    180 iadd           // 計算a+b的值並將結果壓入運算元棧
    181 invokestatic #2 <java/lang/Integer.valueOf>     // 自動裝箱
    184 invokevirtual #13 <java/lang/Long.equals>       // 呼叫equals比較
    187 invokevirtual #9 <java/io/PrintStream.println>
    190 getstatic #8 <java/lang/System.out>
    

    對於Long類,其equals方法程式碼如下(Java 8):

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

    從位元組碼可以看出,g.equals(a+b)也進行了自動拆箱、裝箱過程,但是a+b裝箱後為Integer型別,而gLong型別,因此呼叫equasl會輸出false.

  • System.out.println(g.equals(a+h))對應的位元組碼如下:

    193 aload 7           // 將g壓入運算元棧
    195 aload_1           // 將a壓入運算元棧
    196 invokevirtual #10 <java/lang/Integer.intValue> // 自動拆箱
    199 i2l               // 從a的數值從 int 轉換成 long
    200 aload 8           // 將h壓入運算元棧
    202 invokevirtual #12 <java/lang/Long.longValue>  // 自動拆箱
    205 ladd              // 計算a+h的值並將結果壓入運算元棧
    206 invokestatic #5 <java/lang/Long.valueOf>       // 自動裝箱
    209 invokevirtual #13 <java/lang/Long.equals>      // 呼叫equals比較
    212 invokevirtual #9 <java/io/PrintStream.println>
    

    g.equals(a+h)相比於g.equals(a+b)多了一步型別轉換,a+h裝箱後的型別為Long,因此結果為true.

  • main方法對應的位元組碼如下:

     0 iconst_1
      1 invokestatic #2 <java/lang/Integer.valueOf>
      4 astore_1
      5 iconst_2
      6 invokestatic #2 <java/lang/Integer.valueOf>
      9 astore_2
     10 iconst_3
     11 invokestatic #2 <java/lang/Integer.valueOf>
     14 astore_3
     15 iconst_3
     16 invokestatic #2 <java/lang/Integer.valueOf>
     19 astore 4
     21 sipush 321
     24 invokestatic #2 <java/lang/Integer.valueOf>
     27 astore 5
     29 sipush 321
     32 invokestatic #2 <java/lang/Integer.valueOf>
     35 astore 6
     37 ldc2_w #3 <3>
     40 invokestatic #5 <java/lang/Long.valueOf>
     43 astore 7
     45 ldc2_w #6 <2>
     48 invokestatic #5 <java/lang/Long.valueOf>
     51 astore 8
     53 getstatic #8 <java/lang/System.out>
     56 aload_3
     57 aload 4
     59 if_acmpne 66 (+7)
     62 iconst_1
     63 goto 67 (+4)
     66 iconst_0
     67 invokevirtual #9 <java/io/PrintStream.println>
     70 getstatic #8 <java/lang/System.out>
     73 aload 5
     75 aload 6
     77 if_acmpne 84 (+7)
     80 iconst_1
     81 goto 85 (+4)
     84 iconst_0
     85 invokevirtual #9 <java/io/PrintStream.println>
     88 getstatic #8 <java/lang/System.out>
     91 aload_3
     92 invokevirtual #10 <java/lang/Integer.intValue>
     95 aload_1
     96 invokevirtual #10 <java/lang/Integer.intValue>
     99 aload_2
    100 invokevirtual #10 <java/lang/Integer.intValue>
    103 iadd
    104 if_icmpne 111 (+7)
    107 iconst_1
    108 goto 112 (+4)
    111 iconst_0
    112 invokevirtual #9 <java/io/PrintStream.println>
    115 getstatic #8 <java/lang/System.out>
    118 aload_3
    119 aload_1
    120 invokevirtual #10 <java/lang/Integer.intValue>
    123 aload_2
    124 invokevirtual #10 <java/lang/Integer.intValue>
    127 iadd
    128 invokestatic #2 <java/lang/Integer.valueOf>
    131 invokevirtual #11 <java/lang/Integer.equals>
    134 invokevirtual #9 <java/io/PrintStream.println>
    137 getstatic #8 <java/lang/System.out>
    140 aload 7
    142 invokevirtual #12 <java/lang/Long.longValue>
    145 aload_1
    146 invokevirtual #10 <java/lang/Integer.intValue>
    149 aload_2
    150 invokevirtual #10 <java/lang/Integer.intValue>
    153 iadd
    154 i2l
    155 lcmp
    156 ifne 163 (+7)
    159 iconst_1
    160 goto 164 (+4)
    163 iconst_0
    164 invokevirtual #9 <java/io/PrintStream.println>
    167 getstatic #8 <java/lang/System.out>
    170 aload 7
    172 aload_1
    173 invokevirtual #10 <java/lang/Integer.intValue>
    176 aload_2
    177 invokevirtual #10 <java/lang/Integer.intValue>
    180 iadd
    181 invokestatic #2 <java/lang/Integer.valueOf>
    184 invokevirtual #13 <java/lang/Long.equals>
    187 invokevirtual #9 <java/io/PrintStream.println>
    190 getstatic #8 <java/lang/System.out>
    193 aload 7
    195 aload_1
    196 invokevirtual #10 <java/lang/Integer.intValue>
    199 i2l
    200 aload 8
    202 invokevirtual #12 <java/lang/Long.longValue>
    205 ladd
    206 invokestatic #5 <java/lang/Long.valueOf>
    209 invokevirtual #13 <java/lang/Long.equals>
    212 invokevirtual #9 <java/io/PrintStream.println>
    215 return
    

基礎型別是為了單純使用數值時節省記憶體空間但是無法進行相關函式操作,而包裝型別是為了方便進行相關操作但有佔用更多的記憶體空間,而有時需要用基礎型別,有時又需要用包裝型別,為了避免寫這種重複轉換的程式碼,才提供了自動拆箱/裝箱方便操作。

Reference