1. 程式人生 > >Java中的物件比較

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); }

這個 IntegerCacheInteger 的靜態內部類(巢狀類),而 cahce 是一個數組,作為一個常量池,順序儲存了最小值 low(-128) 到最大值 high(127) 之間的所有整數的 Integer物件。從原始碼中可以看出,當 i 的值在 [ low, high ] 範圍內時,直接返回陣列中對應值的地址,否則就建立一個新的物件。

因此前面 n1==n2 的結果是 true,而 n3==n4 的結果是 false,因為 n1n2 都是引用陣列 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 的字元。

FloatDouble 因為值是不連續的,所以不設定常量池,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,以為它們的引用地址不相同。