Java中的物件比較
關於Java中的數值比較
首先明確一點:
“==” 永遠是在比較地址,而equals是比較內容的值。
當我們書寫這樣的程式碼:
int a = 10;
int b = 10;
JVM 其實是在棧(棧幀中的操作棧?)中查詢 3 這個常量,如已經存在了,變數 a 的引用就指向存放 3 的地址,如果沒有,就建立一個。因此給基本資料型別的變數賦值時,若它們的值相等,那它們在記憶體中的地址也相等。a == b
看似是值的比較,實際上也是常量地址的比較。
包裝類的比較
以Integer
舉例,看一下程式碼:
Integer n1 = 100;
Integer n2 = 100;
Integer n22 = Integer.valueOf(100 );
Integer n222 = new Integer(100);
Integer n3 = 1000;
Integer n4 = 1000;
System.out.println(n1==n2); // true
System.out.println(n1==n22); // true
System.out.println(n1==n222); // false
System.out.println(n3==n4); // false
從輸出結果可以看到,直接賦值 和使用 valueOf()
賦值的方式,兩個變數的比較結果都是 true
,但是 new
一個新的物件來比較就是 false
。這是為什麼呢?
從自動拆裝箱機制
valueOf()
方法,然後我們又知道 ==
比較的是兩個物件的引用地址,這說明直接賦值和使用 valueOf()
方法沒有改變物件的引用地址,因此比較結果是 true
。而 new
一個新物件肯定有不同的地址,所以比較結果是false
。看一下Integer的原始碼:
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
// 最大值 high 可以被修改
...省略
}
high = h;
cache = new Integer[(high - low) + 1];
int j = low;
for(int k = 0; k < cache.length; k++)
cache[k] = new Integer(j++);
...省略
}
}
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
這個 IntegerCache
是 Integer
的靜態內部類(巢狀類),而 cahce
是一個數組,作為一個常量池,順序儲存了最小值 low(-128)
到最大值 high(127)
之間的所有整數的 Integer
物件。從原始碼中可以看出,當 i
的值在 [ low, high ] 範圍內時,直接返回陣列中對應值的地址,否則就建立一個新的物件。
因此前面 n1==n2
的結果是 true
,而 n3==n4
的結果是 false
,因為 n1
、n2
都是引用陣列 cache
中100這個值的地址,而1000超過了範圍,因此建立了新的物件,引用的地址就不一樣了。
有常量池設定的包裝類及其範圍有:
包裝類 | 常量範圍 |
---|---|
Byte | [ -128, 127 ] |
Short | [ -128, 127 ] |
Integer | [ -128, 127 ] |
Long | [ -128, 127 ] |
Character | [ 0, 127 ] |
- 其中
byte
佔8位的,可以表示的值也是在 [ -128, 127 ] 範圍內,所以任何Byte
物件比較結果都是true
。 Integer
常量池的的最大值high
可以通過JVM的引數設定來修改(從原始碼上看應該是這樣),預設值是127。Character
中常量池儲存的是字元編碼中,0 到 127 的字元。
而 Float
、Double
因為值是不連續的,所以不設定常量池,Boolean
也沒必要設定常量池。
String 的比較
String
類也維護有緩衝池,給物件賦值時,會把字串加入到緩衝池中,下次賦值時,若緩衝池中含有相同內容的字串物件,則直接指向它在緩衝池中的地址。
String s1 = "hello";
String s2 = "hello";
System.out.println(s1==s2); // true
String s3 = "hel";
String s4 = "lo";
String s5 = "hel" + "lo";
String s6 = s4 + s5;
System.out.println(s1==s5); // true
System.out.println(s1==s6); // false
在給 s1
賦值後,物件被加入到 String
類的緩衝池中,因此在給 s2
賦值時,s2
的引用直接指向緩衝池中內容為”hello”的物件地址,因此 s1==s2
的結果自然就是 true
了。
編譯階段編譯器會把兩個字串合成一個,因此 s1==s5
的結果也是 true
。但編譯器不會把兩個物件合併,因此 s1==s6
的結果是 false
,以為它們的引用地址不相同。