經常會碰到一些==跟equals()方法的誤用的例子,有時候自己也說不準就弄混了,特地查了些資料寫個小小的總結;
首先大致聲明一下默認的情況Object的==操作跟equals()是等效的;有些同志會說我錯了,會說==操作是比較是否同
一對象的,equals()是比較兩個對象內容是否相等的;這樣的說法可以說對了一小半,錯了一大半;
首先我們來看下Object對象的equals()方法是怎麼實現的:
public boolean equals(Object obj) {
return (this == obj);
}
如何?這裏已經很明顯的告訴大家Object的==操作跟equals()是等效的吧.那有朋友就會問那怎麼實現讓equals()跟==操作不一樣呢?
怎麼讓equals()方法比較兩個對象的內容呢?
答案就是重寫equals()方法;下面需要提到重寫equals()方法要注意的幾點規範:
自反reflexive: x.equals(x)
對稱symmetric: x.equals(y)=>y.equals(x)
傳遞transitive: x.equals(y),<code>y.equals(z) =>x.equals(z)
穩定consistent: x.equals(y) 每次調用的時候總是確定的返回true or false
非空 x.equals(null) return false
從屬於==操作 x==y=> x.equals(y) return true
還有非常重要的一點:改寫equals()方法後,必須也要改寫hashCode()方法; x.equals(y) => x.hashCode()==y.hashCode();
上面解釋了一堆關於重寫equals()方法的原則,現在我們就倒過頭來看看一些已經重寫了equals()方法的具體的例子;首先拿String類型來考察
//*************override equals()****************************
public boolean equals(Object anObject) {
if (this == anObject) {
return true;
}
if (anObject instanceof String) {
String anotherString = (String)anObject;
int n = count;
if (n == anotherString.count) {
char v1[] = value;
char v2[] = anotherString.value;
int i = offset;
int j = anotherString.offset;
while (n-- != 0) {
if (v1[i++] != v2[j++])
return false;
}
return true;
}
}
return false;
}
//*************override hashCode()****************************
public int hashCode() {
int h = hash;
if (h == 0) {
int off = offset;
char val[] = value;
int len = count;
for (int i = 0; i < len; i++) {
h = 31*h + val[off++];
}
hash = h;
}
return h;
}
String類型通過重寫了equals(),hashCode()方法實現了String對象的內容比較;基本類型的對象型比如Integer,Long,Float等類型都
實現了自己的equals()方法;正因爲基本類型都實現了對象的內容比較,所以經常給我們一種想當然的equals()是比較對象內容的錯誤觀點.
如果我們自己寫的類要實現內容比較我們就需要自己重寫equals()跟hashCode()方法,關於hashCode()的重寫建議參考下String的處理方式,
要儘量避免內容不相等的對象產生相同的hashCode()的錯誤,否則就會在hashTable中碰到散列值重複的問題,這個按下不表.
最後額外的講一下String的==的問題.經常碰到別人問String str="abc";String str2=new String("abc") String str3="abc"中有幾個對象的無聊問題.
這裏我給出我自己的解釋共str,str2,str3,"abc", new String("abc")五個對象.其中str,str2,str3是reference對象存於JVM的STACK中,"abc"是一個內存對象容納
了"abc"這個數據,存在於JVM的String池中(str,str3指向的"abc"是同一內存區--String池中的"abc"),new String("abc")是一個內存對象容納了"abc"這個數據,存在
於JVM的Heap 堆中;
按照上面的解釋我們可以明確知道str != str2,因爲他們在內存中是兩個對象(一個在STRING池中一個在HEAP堆中), str == str3因爲他們指向的是String池中的同一對象;
我們通過str="abc";str3="abc"實例化時,JVM會檢查String池中是否已經存在"abc"對象,如果有那麼就把引用指向已經存在的"abc";否則就在String池中新建立一個.
我們通過str2=new String("abc")實例化時,首先這裏分配的內存跟STRING池是沒有任何關係的,無論HEAP堆和STRING池中有無"abc" JVM都會在HEAP堆裏新建立一個
"abc"對象;
檢查對象是否相等
關係運算符==和!=也適用於所有對象,但它們的含義通常會使初涉Java領域的人找不到北。下面是一個例子:
Equivalence { main(String[] args) { Integer n1 = Integer(47); Integer n2 = Integer(47); System.out.println(n1 == n2); System.out.println(n1 != n2); } }
其中,表達式System.out.println(n1 == n2)可打印出內部的布爾比較結果。一般人都會認爲輸出結果肯定先是true,再是false,因爲兩個Integer對象都是相同的。但儘管對象的內容相同,句柄卻是不同的,而==和!=比較的正好就是對象句柄。所以輸出結果實際上先是false,再是true。這自然會使第一次接觸的人感到驚奇。
若想對比兩個對象的實際內容是否相同,又該如何操作呢?此時,必須使用所有對象都適用的特殊方法equals()。但這個方法不適用於“主類型”,那些類型直接使用==和!=即可。下面舉例說明如何使用:
EqualsMethod { main(String[] args) { Integer n1 = Integer(47); Integer n2 = Integer(47); System.out.println(n1.equals(n2)); } }
正如我們預計的那樣,此時得到的結果是true。但事情並未到此結束!假設您創建了自己的類,就象下面這樣:
Value { i; } EqualsMethod2 { main(String[] args) { Value v1 = Value(); Value v2 = Value(); v1.i = v2.i = 100; System.out.println(v1.equals(v2)); } }
此時的結果又變回了false!這是由於equals()的默認行爲是比較句柄。所以除非在自己的新類中改變了equals(),否則不可能表現出我們希望的行爲。不幸的是,要到第7章纔會學習如何改變行爲。但要注意equals()的這種行爲方式同時或許能夠避免一些“災難”性的事件。
大多數Java類庫都實現了equals(),所以它實際比較的是對象的內容,而非它們的句柄。