覆蓋記錄的 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 上找到.