為什麼要重寫 Java 中的 equals 和 hashcode 方法?
資料來源:
Medium 本文重點介紹兩個密切相關的方法:equals()和hashcode()。您將了解它們如何相互交互以及如何正確覆蓋它們。
為什麼我們要重寫 equals() 方法?
在 Java 中,我們不能重載==、
+=、
-+等運算子的行為。他們按照給定的流程工作。例如,考慮
==運算子的操作。
== 運算子如何運作?
它檢查比較的兩個引用是否指向記憶體中的相同實例。只有當兩個引用表示記憶體中的相同實例時,
==運算子才會計算為 true。我們來看看範例程式碼:
public class Person {
private Integer age;
private String name;
..getters, setters, constructors
}
假設在您的程式中,您在不同的位置建立了兩個Person 物件並想要比較它們。
Person person1 = new Person("Mike", 34);
Person person2 = new Person("Mike", 34);
System.out.println( person1 == person2 ); --> will print false!
從商業角度來看,兩者看起來是一樣的,對吧?但對於 JVM 來說它們並不相同。由於它們都是使用
new關鍵字建立的,因此這些實例位於不同的記憶體段中。因此
==運算子將會傳回
false。但是如果我們不能重寫
==運算符,那麼我們要如何告訴 JVM 我們希望這兩個物件得到相同的對待?
這就是.equals()方法發揮作用的地方。您可以重寫
equals()來檢查某些物件的某些欄位是否具有相同的值,以便認為它們相等。您可以選擇要比較的欄位。如果我們說兩個
Person物件只有具有相同的年齡和相同的名稱是相同的,那麼 IDE 將產生類似這樣的內容 來自動建立
equals() :
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Person person = (Person) o;
return age == person.age &&
name.equals(person.name);
}
讓我們回到之前的例子。
Person person1 = new Person("Mike", 34);
Person person2 = new Person("Mike", 34);
System.out.println ( person1 == person2 ); --> will print false!
System.out.println ( person1.equals(person2) ); --> will print true!
是的,我們不能重載
==運算子來按照我們想要的方式比較對象,但是 Java 為我們提供了另一種方式 - equals
()方法,我們可以根據需要重寫該方法。
請記住,如果我們不在類別中提供自訂版本的.equals()(也稱為覆蓋),則Object類別中預先定義的.equals()和==運算子的行為將相同。 預設的
equals()方法繼承自
Object,將檢查正在比較的兩個實例在記憶體中是否相同!
為什麼我們要重寫 hashCode() 方法?
Java 中的某些資料結構(例如
HashSet和
HashMap)是基於應用於這些元素的雜湊函數來儲存其元素。雜湊函數是
hashCode()。如果我們可以選擇重寫
.equals()方法,那麼我們也應該可以選擇重寫
hashCode()方法。這是有原因的。畢竟,繼承自
Object的 hashCode() 的預設實作認為記憶體中的所有物件都是唯一的!但讓我們回到這些雜湊資料結構。這些資料結構有一個規則。
HashSet不能包含重複的值,HashMap不能包含重複的鍵。HashSet是使用
HashMap實現的,每個
HashSet值都作為鍵儲存在
HashMap中。
HashMap是如何運作的?
HashMap是一個具有多個段的原生數組。每個段都有一個鍊錶(
linkedList)。這個鍊錶儲存了我們的密鑰。
HashMap使用hashCode()方法為每個鍵找到正確的 linkedList ,然後迭代該
linkedList的所有元素,並對每個元素應用
equals()方法以檢查該元素是否包含在其中。
不允許有重複的密鑰。當我們將某些內容放入
HashMap時,鍵就會儲存在這些鍊錶之一中。該鍵將儲存在哪個鍊錶中由該鍵的
hashCode()方法的結果顯示。也就是說,如果
key1.hashCode()結果為 4,則
key1將會儲存在現有
LinkedList 陣列的第 4 段。預設情況下,
hashCode()方法為每個實例傳回不同的結果。如果我們有一個預設的
equals(),其行為類似於
==,將記憶體中的所有實例視為不同的對象,那麼就不會有問題。
您可能還記得,在前面的範例中,我們說過,如果Person實例的年齡和名稱相同,則 我們希望它們被視為相等。
Person person1 = new Person("Mike", 34);
Person person2 = new Person("Mike", 34);
System.out.println ( person1.equals(person2) ); --> will print true!
現在讓我們建立一個映射來將這些實例儲存為鍵,並以特定字串作為值對。
Map<Person, String> map = new HashMap();
map.put(person1, "1");
map.put(person2, "2");
在
Person類別中,我們沒有重寫
hashCode方法,但是我們有一個重寫的
equals方法。由於預設
hashCode對於person1.hashCode()和
person2.hashCode()的不同 Java 實例給出不同的結果,因此很有可能會得到不同的結果。
我們的地圖可以以不同的連結清單中的不同的人結束。
這違背了HashMap 的邏輯。
畢竟,HashMap不能有多個相同的按鍵! 重點是繼承自
Object類別的預設hashCode()是不夠的。即使我們重寫了
Person類別的
equals()方法。這就是為什麼我們在重寫
equals方法之後必須重寫
hashCode()方法。現在讓我們解決這個問題。我們需要重寫
hashCode()方法,以便它考慮與
equals()相同的字段,即
age和
name。
public class Person {
private Integer age;
private String name;
..getters, setters, constructors
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Person person = (Person) o;
return age == person.age &&
name.equals(person.name);
}
@Override
public int hashCode() {
int prime = 31;
return prime*Objects.hash(name, age);
}
在
hashCode()方法中,我們使用了一個簡單的值(您可以使用任何其他值)。不過,建議使用質數來減少問題。讓我們再次嘗試將這些鍵儲存在
HashMap中:
Map<Person, String> map = new HashMap();
map.put(person1, "1");
map.put(person2, "2");
person1.hashCode()和
person2.hashCode()是相同的。假設它們是 0。HashMap
將轉到段 0,
LinkedList在其中將person1保存為值為「1」的鍵。第二種情況,當
HashMap再次到桶0儲存key為「2」
的person2時,會發現那裡已經存在另一個與它相等的key。這樣它將覆蓋以前的密鑰。而只有關鍵的
person2才會存在於我們的
HashMap中。
這就是我們了解HashMap規則的工作原理的方式,該規則規定不能使用多個相同的鍵!
但是,請記住,不相等的實例可以具有相同的雜湊碼,並且相等的實例必須傳回相同的雜湊碼。
GO TO FULL VERSION