一、案例
我們先來看下這個例子:
public class IntegerTest {
public static void main(String[] args) {
Integer a = 127;
Integer b = 127;
System.out.println(a == b);
a = 128;
b = 128;
System.out.println(a == b);
a = -128;
b = -128;
System.out.println(a == b);
a = -129;
b = -129;
System.out.println(a == b);
}
}
程式運作結果:
true
false
true
false
看到這個結果,是不是很疑惑,不應該都是true嗎?
二、剖析
要弄懂這其中的緣由,我們要先明白上面的程式到底做了什麼?
javap是JDK自帶的反彙編器,可以檢視java編譯器為我們生成的位元組碼。通過它,我們可以對照源代碼和位元組碼,進而了解很多編譯器内部的工作。
于是,我們通過javap指令反編譯IntegerTest.class位元組碼檔案,得到結果如下:
$ javap -c IntegerTest
▒▒▒▒: ▒▒▒▒▒▒▒ļ▒IntegerTest▒▒▒▒com.lian.demo.IntegerTest
Compiled from "IntegerTest.java"
public class com.lian.demo.IntegerTest {
public com.lian.demo.IntegerTest();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."": ()V
4: return
public static void main(java.lang.String[]);
Code:
0: bipush 127
2: invokestatic #2 // Method java/lang/Integer.valueOf: (I)Ljava/lang/Integer;
5: astore_1
6: bipush 127
8: invokestatic #2 // Method java/lang/Integer.valueOf: (I)Ljava/lang/Integer;
11: astore_2
12: getstatic #3 // Field java/lang/System.out:Ljava/ io/PrintStream;
15: aload_1
16: aload_2
17: if_acmpne 24
20: iconst_1
21: goto 25
24: iconst_0
25: invokevirtual #4 // Method java/io/PrintStream.printl n:(Z)V
28: sipush 128
31: invokestatic #2 // Method java/lang/Integer.valueOf: (I)Ljava/lang/Integer;
34: astore_1
35: sipush 128
38: invokestatic #2 // Method java/lang/Integer.valueOf: (I)Ljava/lang/Integer;
41: astore_2
42: getstatic #3 // Field java/lang/System.out:Ljava/ io/PrintStream;
45: aload_1
46: aload_2
47: if_acmpne 54
50: iconst_1
51: goto 55
54: iconst_0
55: invokevirtual #4 // Method java/io/PrintStream.printl n:(Z)V
58: bipush -128
60: invokestatic #2 // Method java/lang/Integer.valueOf: (I)Ljava/lang/Integer;
63: astore_1
64: bipush -128
66: invokestatic #2 // Method java/lang/Integer.valueOf: (I)Ljava/lang/Integer;
69: astore_2
70: getstatic #3 // Field java/lang/System.out:Ljava/ io/PrintStream;
73: aload_1
74: aload_2
75: if_acmpne 82
78: iconst_1
79: goto 83
82: iconst_0
83: invokevirtual #4 // Method java/io/PrintStream.printl n:(Z)V
86: sipush -129
89: invokestatic #2 // Method java/lang/Integer.valueOf: (I)Ljava/lang/Integer;
92: astore_1
93: sipush -129
96: invokestatic #2 // Method java/lang/Integer.valueOf: (I)Ljava/lang/Integer;
99: astore_2
100: getstatic #3 // Field java/lang/System.out:Ljava/ io/PrintStream;
103: aload_1
104: aload_2
105: if_acmpne 112
108: iconst_1
109: goto 113
112: iconst_0
113: invokevirtual #4 // Method java/io/PrintStream.printl n:(Z)V
116: return
}
标号2的code調用了靜态的valueOf方法解析Integer執行個體,也就是說Integer a = 127; 在編輯期進行了自動裝箱,即把基本資料類型轉換為包裝類型。
從JDK1.5就開始引入了自動拆裝箱的文法功能,也就是系統将自動進行基本資料類型和與之相對應的包裝類型之間的轉換,這使得程式員書寫代碼更加友善。
裝箱過程是通過調用包裝器的valueOf方法實作的。
拆箱過程是通過調用包裝器的xxxValue方法實作的(xxx表示對應的基本資料類型)。
當給a指派時,實際上是調用了Integer.valueOf(int i)方法。其JDK 8源碼如下:
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() {}
}
看到了沒,當賦的基本資料類型值不在[-128, 127]之間,會去Java堆記憶體中new一個對象出來,顯然它們不是兩個不同的對象,是以結果false;
而值在[-128, 127]之間,會直接從IntegerCache中擷取,也就是從緩存中取值,不用再建立新的對象,即同一個對象,是以結果true。
關系操作符“==”生成的是一個boolean結果,它們計算的是操作數的值之間的關系。如果是基本類型則直接判斷其值是否相等,如果是對象則判斷是否是同一個對象的引用,即其引用變量所指向的對象的位址是否相同。
三、結論
在阿裡巴巴Java開發手冊中,它是這麼描述的:
【強制】所有的相同類型的包裝類對象之間值的比較,全部使用equals方法比較。
說明:對于Integer var = ? 在-128 至 127範圍内的指派,Integer對象是在IntegerCache.cache産生,會複用己有對象,這個區間内的Integer值可以直接使用==進行判斷,但是這個區間之外的所有資料,都會在堆上産生,并不會複用己有對象,這是一個大坑,推薦使用equals方法進行判斷。
可能你還會問,問啥是equals方法?這就要看equals方法到底做了什麼?可以參考《Java中關系操作符“==”和equals()方法的差別》
我們來看下Integer類中equals源碼:
public boolean equals(Object obj) {
if (obj instanceof Integer) {
return value == ((Integer)obj).intValue();
}
return false;
}
直接通過intValue()方法拆箱,即将包類型轉換為基本資料類型。是以equals方法比較的是它們的值了。
四、注意事項
包裝類型
緩存指派範圍
基本資料類型
二進制數
Boolean
全部緩存
boolean
1
Byte
[-128, 127]
byte
8
Character
<=127
char
16
Short
[-128, 127]
short
16
Integer
[-128, 127]
int
32
Long
[-128, 127]
long
64
Float
沒有緩存
float
32
Double
沒有緩存
double
64
是以兩個同類型的Float或Double類型的==比較永遠都是傳回false。