覆蓋記錄的 hashCode() 和 equals()
一、簡介
Java 14 引入了record
的概念,作為傳遞不可變數據對象的一種簡單且更好的方法。 record
只有Class
具有的最基本的方法,構造函數和 getter setter,因此是類的一種受限形式,類似於 Java 中的Enum
。 record
是一種普通的數據載體,是一種用於傳遞未更改數據的類的形式。
在本教程中,我們將討論如何覆蓋記錄的默認hashCode()
和equals()
實現record.
2. hashCode()
和equals()
方法
Java 對Object
類定義了equals()
和hashCode()
方法。由於 Java 中的所有類都繼承自Object
類,因此它們也具有方法的默認實現。
equals()
方法旨在斷言兩個對象相等,默認實現意味著如果兩個對象具有相同的標識,則它們是相等的。 hashCode()
方法返回一個基於當前類實例的整數值,它與相等性定義一起實現。
record,
是 Java 中Class
的一種受限形式,
它的默認實現是equals()
、 hashCode(),
和toString().
我們可以使用new
關鍵字實例化記錄。我們還可以比較一個記錄的兩個實例是否相等,就像我們對一個類所做的那樣。
3. record
的hashCode()
和equals()
的默認實現
R
類型的任何record
都直接繼承自java.lang.Record.
默認情況下,Java 提供了這兩種方法的默認實現以供使用。
默認的equals()
實現遵循Object's
equals()
方法的約定。此外,當我們通過複製另一個記錄實例的所有屬性來創建新記錄實例時,這兩個記錄實例必須相等。這一點很重要,因為record
的概念是成為不被更改的數據的數據載體。
假設我們有一個Person
類型的記錄,我們創建了該記錄的兩個實例:
public record Person(String firstName, String lastName, String SSN, String dateOfBirth) {};
Person rob = new Person("Robert", "Frost", "HDHDB223", "2000-01-02");
Person mike = new Person("Mike", "Adams", "ABJDJ2883", "2001-01-02");
請注意,我們可以這樣做,因為records
提供默認構造函數,考慮到開箱即用的所有記錄字段。此外,我們還有一個開箱即用的equals()
可供使用,使我們能夠比較Person
的實例:
@Test
public void givenTwoRecords_whenDefaultEquals_thenCompareEquality() {
Person robert = new Person("Robert", "Frost", "HDHDB223", "2000-01-02");
Person mike = new Person("Mike", "Adams", "ABJDJ2883", "2001-01-02");
assertNotEquals(robert, mike);
}
默認的hashCode()
實現返回一個散列碼(整數)值,該散列碼(整數)值是通過組合記錄屬性的所有散列值並遵循Object
的散列碼協定得出的。從相同組件創建的兩條記錄也必須具有相同的hashCode
值:
@Test
public void givenTwoRecords_hashCodesShouldBeSame() {
Person robert = new Person("Robert", "Frost", "HDHDB223", "2000-01-02");
Person robertCopy = new Person("Robert", "Frost", "HDHDB223", "2000-01-02");
assertEquals(robert.hashCode(), robertCopy.hashCode());
}
4. 自定義實現hashCode()
和equals()
record
Java 確實提供了覆蓋equals()
和hashCode()
方法的默認實現的能力。例如,假設我們決定如果標題和發行年份相同,就足以斷言兩條Movie
記錄(具有多個屬性)相等。
在這種情況下,我們可以選擇覆蓋默認實現,它認為每個屬性都斷言與我們自己的相等:
record Movie(String name, Integer yearOfRelease, String distributor) {
@Override
public boolean equals(Object other) {
if (this == other) {
return true;
}
if (other == null) {
return false;
}
Movie movie = (Movie) other;
if (movie.name.equals(this.name) && movie.yearOfRelease.equals(this.yearOfRelease)) {
return true;
}
return false;
}
此實現有點類似於 Java Class
的equals()
的任何其他自定義實現:
- 如果兩個實例相同,則它們必須相等
- 如果另一個實例為
null,
相等性失敗 - 將另一個對象轉換為記錄類型後,如果
name
和yearOfRelease
屬性與當前對象的相同,則它們必須相等
請注意,我們沒有添加條件來檢查other
對像是否屬於Movie
類型,因為記錄本質上是最終的。
然而,在檢查兩個Movie
記錄是否相等時,如果發生衝突,編譯器將轉向hashCode()
來確定是否相等。因此,重寫hashCode()
方法的實現也很重要:
@Override
public int hashCode() {
return Objects.hash(name, yearOfRelease);
}
我們現在可以正確地測試兩條Movie
記錄的相等性:
@Test
public void givenTwoRecords_whenCustomImplementation_thenCompareEquality() {
Movie movie1 = new Movie("The Batman", 2022, "WB");
Movie movie2 = new Movie("The Batman", 2022, "Dreamworks");
assertEquals(movie1, movie2);
assertEquals(movie1.hashCode(), movie2.hashCode());
}
5.何時覆蓋默認實現
Java 規範期望record
的equals()
的任何自定義實現滿足記錄的副本必須等於原始記錄的規則。但是,由於 Java 記錄旨在傳遞不可變數據記錄,因此使用 Java 提供的equals()
和hashCode()
的默認實現通常是安全的。
然而, records
是淺層不可變的。這意味著如果record
具有可變屬性(例如List
,它們不會自動不可變。在默認實現中,只有當兩個實例的所有屬性都相等時,兩個記錄才相等,而不管屬性是原始類型還是引用類型。
這對record
實例服務於它們的目的提出了挑戰,最好覆蓋默認的equals()
和hashCode()
實現。它也非常適合用作Map.
這意味著我們應該小心處理帶有可能可變數據元素的記錄。
六,結論
在本文中,我們了解了records
如何為我們提供equals()
和hashCode()
方法的默認實現。我們還研究瞭如何使用自定義實現覆蓋默認實現。
我們還研究了何時應該考慮覆蓋默認行為。
與往常一樣,所有代碼示例都可以在 GitHub 上找到.