【Java】如何重寫hashCode()和equals()方法


如之前提到的對hashCode()的分析:【Java】hashCode特性、算法和作用

1. 如何重寫hashCode()方法

Google首席Java架構師Joshua Bloch在他的著作《Effective Java》中提出了一種簡單通用的hashCode算法

  1. 初始化一個整形變量,爲此變量賦予一個非零的常數值,比如int result = 17;
  2. 選取equals方法中用於比較的所有域,然後針對每個域的屬性進行計算:
    (1) 如果是boolean值,則計算 f ? 1:0
    (2) 如果是byte\char\short\int,則計算**(int)f**
    (3) 如果是long值,則計算**(int)(f ^ (f >>> 32))**
    (4) 如果是float值,則計算Float.floatToIntBits(f)
    (5) 如果是double值,則計算Double.doubleToLongBits(f),然後返回的結果是long,再用規則(3)去處理long,得到int
    (6) 如果是對象應用,如果equals方法中採取遞歸調用的比較方式,那麼hashCode中同樣採取遞歸調用hashCode的方式。否則需要爲這個域計算一個範式,比如當這個域的值爲null的時候,那麼hashCode 值爲0
    (7) 如果是數組,那麼需要爲每個元素當做單獨的域來處理。如果你使用的是1.5及以上版本的JDK,那麼沒必要自己去重新遍歷一遍數組,java.util.Arrays.hashCode方法包含了8種基本類型數組和引用數組的hashCode計算,算法同上,
      java.util.Arrays.hashCode(long[])的具體實現:
public static int hashCode(long a[]) {
        if (a == null)
            return 0;
 
        int result = 1;
        for (long element : a) {
            int elementHash = (int)(element ^ (element >>> 32));
            result = 31 * result + elementHash;
        }
 
        return result;
}
  1. 對於涉及到的各個字段,採用第二步中的方式,將其依次應用於下式:
result = result * 31 + [hashCode];

補充說明一點:
如果初始值result不取17而取0的話,則對於hashCode爲0的字段來說就沒有區分度了,這樣更容易產生衝突。比如兩個自定義類中,一個類比另一個類多出來一個或者幾個字段,其餘字段全部一樣,分別new出來2個對象,這2個對象共有的字段的值全是一樣的,而對於多來的那些字段的值正好都是0,並且在計算hashCode時這些多出來的字段又是最先計算的,這樣的話,則這兩個對象的hashCode就會產生衝突。還是那句話,hashCode方法的實現沒有最好,只有更好。

2. hashCode() 重寫固定模板

故總結出hashCode()重寫的固定模板如下:

	/**
	 * 重寫hashCode方法
	 */
	@Override
	public int hashCode() {
		int result = 17;
		// boolean 類型
		result = 31 * result + (this.mBoolean == flase ? 0 : 1);
		// int 類型
		result = 31 * result + this.mInt;
		// float 類型
		result = 31 * result + Float.floatToIntBits(this.mFloat);
		// long 類型
		result = 31 * result + (int)(this.mLong ^ (this.mLong >>> 32));
		// double 類型
		result = 31 * result + Float.valueOf(Double.doubleToLongBits(this.mDouble)).hashCode();
		// String 類型
		result = 31 * result + (this.mString == null ? 0 : this.mString.hashCode());
		// Object 類型
		result = 31 * result + (this.mObj == null ? 0 : this.mObj.hashCode());
		return result;
	}

例子代碼中的類:

class Worker {
	private String name;
	private int age;
	private double salary;
	//...略
}
	/**
	 * 重寫hashCode方法
	 */
	@Override
	public int hashCode() {
		int result = 17;
		// int 類型
		result = 31 * result + this.age;
		// double 類型
		result = 31 * result + Float.valueOf(Double.doubleToLongBits(this.salary)).hashCode();
		// String 類型
		result = 31 * result + (this.name == null ? 0 : this.name.hashCode());
		return result;
	}

驗證hashCode()方法重寫

3. 如何重寫equals()方法

碼來:

	/**
	 * 5.5 爲Worker添加equals方法,轉字符串比較
	 */
	@Override
	public boolean equals(Object obj) {
		// 相同判斷
		if (this == obj) {
			return true;
		}
		
		// null判斷
		if (obj == null) {
			return false;
		}
		
		// 類型一致判斷
		if (this.getClass() != obj.getClass()) {
			return false;
		}
		
		// 拆箱操作(類型一致)
		Worker w = (Worker)obj;
		
		// 比較內容(比較所有成員的值)
		if (this.toString().equals(w.toString())) {
			return true;
		}
		
		return false;
	}

驗證如上圖(本文就1張圖)。

重寫覆蓋父類Object.equals()方法,五步走:
* 1.判斷引用地址是否相同
* 2.判斷引用地址是否爲空
* 3.確認對象類型是否一致
* 4.轉型 - 向下轉型拆箱
* 5.比較對象中的實際內容

hashCode與equals的關係總結:
1.hashcode相等,兩個對象不一定相等,需要通過equals方法進一步判斷;
2.hashcode不相等,兩個對象一定不相等
3.equals方法爲true,則hashcode肯定一樣
4.equals方法爲false,則hashcode不一定不一樣。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章