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
輸出結果表明i1
和i2
指向的是同一個物件,而i3
和i4
指向的是不同的物件。Java 8中Integer
的valueOf()
方法的具體實現如下:
// 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中Double
的valueOf()
方法的具體實現如下,不難理解答案為何都是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、Float
的valueOf()
方法的實現是類似的:
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
每次都會建立一個新物件,而除了Float
和Double
外的其他包裝類,都會快取包裝類物件,減少需要建立物件的次數,節省空間,提升效能。 - 從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==d
和e == 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
型別,而g
是Long
型別,因此呼叫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
基礎型別是為了單純使用數值時節省記憶體空間但是無法進行相關函式操作,而包裝型別是為了方便進行相關操作但有佔用更多的記憶體空間,而有時需要用基礎型別,有時又需要用包裝型別,為了避免寫這種重複轉換的程式碼,才提供了自動拆箱/裝箱方便操作。