1. 程式人生 > >Java-正確使用equals和hashCode方法

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()就不會出現這個問題。