原創文章,轉載請標註出處:《Java基礎系列-equals方法和hashCode方法》
概述
equals方法和hashCode方法都是有Object類定義的。
public class Object {
public native int hashCode();
public boolean equals(Object obj) {
return (this == obj);
}
}
任何的類都是Object類的子類,所有它們預設都擁有這兩個方法。
equals方法用於定義兩個物件的比較方式,而hashCode方法是native方法,主要使用者計算物件的hash值。
equals
equals方法主要用於定義兩個物件的比較方式,預設的比較方式是比較記憶體地址,相對於基本型別來說就是值,而相對於引用型別來說就是堆中具體物件的地址。那麼就只有值相同的基本型別,和同一個物件的兩個引用才能相等。但是在我們實際業務系統中,兩個物件的相等一般指的是兩個物件的內容相同(邏輯相同),而不是說它兩個是同一個物件,這種情況使用預設的equals就無法實現相等(因為兩個不同物件地址值一定不同),這時候我們就需要對equals方法進行重寫,定義新的比較方式。
準則
- 自省性:對於非null的x,存在:x.equals(x)返回true
- 對稱性:對於非null的x和y,存在:x.equals(y)==y.equals(x)
- 傳遞性:對於非null的x、y、z,存在:當x.equals(y)返回true,y.equals(z)返回true,則x.equals(z)一定為true
- 一致性:對於非null的x和y,多次呼叫x.equals(y)所得的結果是不變的
-
非空性:對於非null的x,存在x.equals(null)返回false
重寫
其實Java中已經為我們展示瞭如何重equals方法了,最經典的就是String的equals方法:
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
public boolean equals(Object anObject) {
// 首先判斷兩個物件是不是同一個,地址相同否
if (this == anObject) {
return true;
}
// 判斷給定的物件是否是String型別,這裡instanceof關鍵字是重寫equals方法時經常使用的一個關鍵字
// instanseof用於判斷右邊的型別是否是當前物件的型別或者超型別,超介面型別等
if (anObject instanceof String) {
String anotherString = (String)anObject;
int n = value.length;
// 校驗兩個字串的長度相同否
if (n == anotherString.value.length) {
char v1[] = value;
char v2[] = anotherString.value;
int i = 0;
// 迴圈校驗兩個字串中的每個字元是否相同
while (n-- != 0) {
if (v1[i] != v2[i])
return false;
i++;
}
return true;
}
}
return false;
}
}
注意,使用instanceof在針對存在子類的情況下,可能會出現違反對稱性和傳遞性的情況,為了避免這種情況,可以通給getClass的方式比較型別。
自定義重寫:
public class EqualsTest {
private int id;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
@Override
public boolean equals(Object obj) {
// 滿足非空性
if(obj == null){
return false;
}
// 滿足自省性
if(this == obj){
return true;
}
// 滿足對稱性、傳遞性、一致性
if(this.getClass() == obj.getClass()
&& this.getClass().getClassLoader() == obj.getClass().getClassLoader()
&& this.id == ((EqualsTest)obj).getId()){
return true;
}
return false;
}
}
注意:這裡如果是有不同的類載入器載入的同一類的例項也是無法相等的。
hashCode
hashCode一般用於計算物件的hash值,它在類重寫equals的時候一起重寫,重寫它的目的是為了保證equals相同的兩個物件的hashCode結果一致,為什麼要保證這一點呢,那就歸結到java中的那幾個基於Hash實現的集合上了,比如HashMap、HashSet等,這些集合需要用到物件的hash值來參與計算定位。
使用hashCode的目的就是為了雜湊元素,最終元素能否雜湊均勻和hashCode的實現息息相關,即為hash函式。
實現方式
- 鏈地址法(理解):在出現hash衝突的時候,在這個位置再插入新元素,並與原有元素形成一個連結串列,類似於HashMap的實現方式
- 開放定址法(瞭解):在出現hash衝突的時候,在當前位置的附近尋找空位來存放新元素,這種方式只需要一種資料結構,不需要引入新的資料結構。其實就是為每個hash結果準備一個探查序列,用來存放發生hash衝突的元素。
- 線性探查法:當出現hash衝突,則在當前位置逐個向後尋找空位,將新元素儲存到找到的第一個空位,當找到最後時,需要折返到一開頭繼續查詢。由於探查序列固定,所以會引發一次叢集問題。
- 二次探查法:出現衝突,不再逐個順序探查,而是由某種函式計算的結果序列來探查,這個函式依賴於開始下標的平方,所以叫二次探查,開始下標的不同,序列就不相同,不同序列中會有重複的下標,由於每個下標開始的探查序列是固定的,所以會引發小規模叢集,即二次叢集問題。
- 雙重雜湊法:要解決群集,就要想辦法讓相同hash結果的序列不同,最好讓序列函式依賴於元素本身,保證當元素不同時,即使hash結果一致,但一旦發生衝突,不同的元素的序列是不同的(因為序列還要依賴元素本身,元素不同,序列結果就會不同),這樣存在兩個依賴變數的探查方法,可以極大的避免叢集問題。
- 再HASH法(知道)
- 建立公共溢位區法(知道)
hashCode的實現方式並不是隨手而來的,需要考慮各種情況,選擇合適的方式來實現,舉個例子,在Java的HashMap集合中,採用的就是鏈地址法來處理hash衝突。
參考: