Java-正確使用equals和hashCode方法
基本摘抄自Java 中正確使用 hashCode 和 equals 方法
hashCode()和equals()定義在Object類中,這個類是所有java類的基類,所以所有的java類都繼承這兩個方法。
1.equals
equals要遵守的通用約定(equals方法實現了等價關係):
1)自反性:x.equals(x)一定返回true
2)對稱性:x.equals(y)返回true當且僅當y.equals(x)
3)傳遞性:x.equals(y)且y.equals(z),則x.equals(z)為true
4)一致性:若x.equals(y)返回true,則不改變x,y時多次呼叫x.equals(y)都返回true
5)對於任意的非空引用值x,x.equals(null)一定返回false。
當重寫完equals方法後,應該檢查是否滿足對稱性、傳遞性、一致性。(自反性、null通常會自行滿足)
1.1 equals重寫預設實現
有的時候程式要求我們必須改變一些物件的預設實現。
來看看這個例子,讓我們建立一個簡單的類Person
public class Person {
private String id;
private String name;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "id:"+this.getId()+" name:"+this.getName();
}
}
上面的Person類只是有一些非常基礎的屬性和getter、setter.現在來考慮一個你需要比較兩個person的情形。
public class EqualTest {
public static void main(String[] args) {
Person p1 = new Person();
Person p2 = new Person();
p1.setId("1"); p1.setName("xwq");
p2.setId("1"); p2.setName("xwq");
System.out.println(p1.equals(p2));
}
}
毫無疑問,上面的程式將輸出false,但是,事實上上面兩個物件代表的是通過一個person。真正的商業邏輯希望我們返回true。
為了達到這個目的,我們需要重寫equals方法。
@Override
public boolean equals(Object o) {
if(o == null) return false;
if(o == this) return true;//提高效率
if(o.getClass() != this.getClass())
return false;
//或者使用instance關鍵字判斷
// if(!(o instanceof Person))
// return false;
Person p1 = (Person)o;
//如果元素是陣列,可使用Arrays.deepEquals(array1,array2);進行判斷
return this.getId().equals(p1.getId()) && this.getName().equals(p1.getName());
}
在上面的類中新增這個方法,EauqlsTest將會輸出true。
1.2 編寫equals開發方法 的建議
當equals引數不屬於同一類時,且具有繼承關係時,instanceof的檢測結果將不滿足對稱性。
如:c是p的子類,如果在equals中用instanceof檢測,那麼:
p.equals(c) 將返回true;
c.equals(p) 將返回false或者丟擲異常。
1、顯示引數命名為otherobject,稍後強制轉換為叫other的變數。
2、檢測this==otherobject
3、檢測this==null
4、檢測getclass()!=otherobject.getclass()
如果所有的子類都擁有統一的語義,就使用instanceof檢測
otherobject instanceof classname
5、將otherobject轉換為相應型別的變數
6、比較所有的域
7、若在子類中重新定義了equals開發方法 ,則需在子類中包含呼叫super.equals(other)
2.hashCode
hashCode()是HashTable、HashMap和HashSet使用的。hashCode()方法被用來獲取給定物件的唯一整數。這個整數被用來確定物件被儲存在HashTable類似的結構中的位置。預設的,Object類的hashCode()方法返回這個物件儲存的記憶體地址的編號。
hash雜湊演算法,使得在hash表中查詢一個記錄速度變O(1). 每個記錄都有自己的hashcode,雜湊演算法按照hashcode把記錄放置在合適的位置. 在查詢一個記錄,首先先通過hashcode快速定位記錄的位置.然後再通過equals來比較是否相等。如果hashcode沒找到,則必定不equal,元素不存在於雜湊表中;即使找到了,也只需執行hashcode相同的幾個元素的equal,如果不equal,還是不存在雜湊表中。
2.1 hashcode重寫預設實現
來看看這個例子,
import java.util.HashSet;
import java.util.Set;
public class EqualTest {
public static void main(String[] args) {
Person p1 = new Person();
Person p2 = new Person();
p1.setId("1"); p1.setName("xwq");
p2.setId("1"); p2.setName("xwq");
Set<Person> s = new HashSet<Person>();
s.add(p1);
s.add(p2);
System.out.println(s);
}
}
上面的程式輸出的結果是兩個。
如果兩個employee物件equals返回true,Set中應該只儲存一個物件才對,問題在哪裡呢?
我們忘掉了第二個重要的方法hashCode(),因為hashCode()的預設實現是物件在記憶體中的記憶體地址,所以p1和p2都被插入到HashSet中。
如果重寫equals()方法必須要重寫hashCode()方法。我們加上下面這個方法,程式將執行正確。
需要注意記住的事情
- 儘量保證使用物件的同一個屬性來生成hashCode()和equals()兩個方法。在我們的案例中,我們使用人員id。
- eqauls方法必須保證一致(如果物件沒有被修改,equals應該返回相同的值)
- 任何時候只要a.equals(b),那麼a.hashCode()必須和b.hashCode()相等。
- 兩者必須同時重寫。
當使用ORM的時候特別要注意的
- 如果你使用ORM處理一些物件的話,你要確保在hashCode()和equals()物件中使用getter和setter而不是直接引用成員變數。因為在ORM中有的時候成員變數會被延時載入,這些變數只有當getter方法被呼叫的時候才真正可用。
- 例如在我們的例子中,如果我們使用p1.id == p2.id則可能會出現這個問題,但是我們使用p1.getId() == p2.getId()就不會出現這個問題。