DDD實體值物件的equals和hashcode方法實現 - wimdeblauwe
Java中的所有類均繼承自java.lang.Object,它有equals()和hashCode()方法,這兩個方法是你定義自己的類時通常應該重寫兩個重要方法。
equals()比較兩個個物件以檢查它們是否代表同一物件是很重要的;如果將物件放在HashMap或HashSet中,hashCode()則很重要。它提供了那些資料結構所使用的雜湊值。
即使您不瞭解領域驅動設計,您也可能聽說過實體和值物件。如果您還沒有,請簡要回顧一下它們之間的區別:
- 實體:在應用程式域中具有唯一標識的物件。例如,User或Invoice。
- 值物件:僅因它們表示的值而重要的物件。例如,一個Money或Temperature物件。通常,這些物件是不可變的。
值物件的equals和hashcode方法
public class Temperature { private final double value; private final Unit unit; public Temperature(double value, Unit unit) { this.value = value; this.unit = unit; } public double getValue() { return value; } public Unit getUnit() { return unit; } enum Unit { KELVIN, CELCIUS, FAHRENHEIT; } } |
對於值物件,我們想說明所有屬性相等時物件是相等的。equals()實現應該是這樣的:
public class Temperature { ... @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } Temperature that = (Temperature) o; return Double.compare(that.value, value) == 0 && unit == that.unit; } @Override public int hashCode() { return Objects.hash(value, unit); } ... } |
- 如果傳入的物件與當前物件是相同的引用(在記憶體中),則相同。
- 一個物件永遠不能等於null也不能等於另一個類的物件。
- 我們可以安全地轉換傳入的物件,因為我們確定它與該物件屬於同一類。
- 比較傳入物件和當前物件的每個屬性
- 使用JDKObjects.hash()方法使用當前物件的所有屬性生成雜湊碼。
現在我們可以驗證Temperature具有相同屬性的2個物件是否相等:
@Test void testEqualTemperature() { Temperature temperature1 = new Temperature(37.0, Temperature.Unit.CELCIUS); Temperature temperature2 = new Temperature(37.0, Temperature.Unit.CELCIUS); boolean equal = temperature1.equals(temperature2); assertTrue(equal); } |
測試hashCode()實現:
@Test void testHashCodeForEqualObjects() { Temperature temperature1 = new Temperature(37.0, Temperature.Unit.CELCIUS); Temperature temperature2 = new Temperature(37.0, Temperature.Unit.CELCIUS); int hashCode1 = temperature1.hashCode(); int hashCode2 = temperature2.hashCode(); assertThat(hashCode1).isEqualTo(hashCode2); } |
實體的equals和hashcode方法
對於實體而言,真正重要的是識別符號。我們希望看到2個例項具有與同一事物相同的識別符號,即使其他屬性不同也是如此。假設這個簡單的User實體:
import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.Id; @Entity public class User { @Id @GeneratedValue private Long id; private String name; protected User() { } public User(String name) { this.name = name; } public Long getId() { return id; } public String getName() { return name; } public void setName(String name) { this.name = name; } } |
由於我們只關心該id領域,因此一個簡單的實現看起來像這樣:
@Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } User user = (User) o; return Objects.equals(id, user.id); } @Override public int hashCode() { return Objects.hash(id); } |
不幸的是,這是錯誤的。問題在於該id欄位是由資料庫生成的,並且僅在物件持久化之後才填寫。因此,對於同一個物件,id最初是null,然後在將其儲存在資料庫中之後獲得某個值。
幸運的是,Vlad Mihalcea向我們展示了如何正確實現這一點:
@Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } User user = (User) o; return id != null && id.equals(user.id); } @Override public int hashCode() { return getClass().hashCode(); } |
兩個重要注意事項:
- 如果id填充,我們會看到User等同的的情況,如果在兩個User例項沒有被儲存在資料庫中,則永遠不會相等。
- Hashode使用硬編碼的值,因為在建立物件的時間和將其保留在資料庫中的時間之間不允許hashCode值發生變化。
請參閱如何使用JPA實體識別符號(主鍵)實現equals和hashCode以獲得有關此內容的更詳細資訊。
資料庫主鍵生成的實體的兩個方法
如果你不喜歡,上面JPA實體實現equals()和hashCode()的方式,那麼就可以採取不同的路線。在建立物件之前生成主鍵時,有兩個優點:
- id可在建構函式中賦值,你不能建立“無效”物件。
- equals()和hashCode()方法可以簡化為僅考慮id。
import org.springframework.util.Assert; import javax.persistence.Entity; import javax.persistence.Id; @Entity public class Book { @Id private Long id; private String name; protected Book() { } public Book(Long id, String name) { Assert.notNull(id, "id should not be null"); Assert.notNull(name, "name should ot be null"); this.id = id; this.name = name; } public Long getId() { return id; } public String getName() { return name; } public void setName(String name) { this.name = name; } } |
Book實體沒有@GeneratedValue註釋,因此我們將需要在構造時傳遞一個值。
現在我們知道該id欄位永遠不會null,我們可以使用以下實現:
@Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } Book book = (Book) o; return id.equals(book.id); } @Override public int hashCode() { return Objects.hash(id); } |
測試程式碼:
@Test void testEquals() { Book book1 = new Book(1L, "Taming Thymeleaf"); Book book2 = new Book(1L, "Taming Thymeleaf"); assertThat(book1).isEqualTo(book2); } @Test void testEquals() { Book book1 = new Book(1L, "Taming Thymeleaf"); Book book2 = new Book(1L, "Totally different title"); assertThat(book1).isEqualTo(book2); } |
測試equals和hashCode實現
為確保正確實現您的方法,請使用EqualsVerifier庫包。
將其新增到您的pom.xml:
<dependency> <groupId>nl.jqno.equalsverifier</groupId> <artifactId>equalsverifier</artifactId> <version>3.6</version> <scope>test</scope> </dependency> |
並編寫測試:
@Test public void equalsContract() { EqualsVerifier.forClass(Temperature.class).verify(); } |
這將測試是否equals()是自反的,對稱的,可傳遞的和一致的。它還會測試是否hashCode()遵守java.lang.ObjectAPI中定義的合同。
結論
要正確實現equals()and hashCode(),首先確定您的物件是值物件還是實體很重要。如果是其中之一,則可以遵循部落格中列出的規則。如果兩者都不是(例如Controller,Service,Repository,...),那麼你可能不希望覆蓋的方法。
相關文章
- String的equals和hashCode方法
- TypeScript如何實現DDD的值物件?TypeScript物件
- 物件只定義了Equals和Hashcode方法之一的漏洞物件
- DDD的實體、值物件、聚合根的基類和介面:設計與實現物件
- 搞懂 Java equals 和 hashCode 方法Java
- 自動生成hashcode和equals方法
- Java基礎系列-equals方法和hashCode方法Java
- 重新實現hashCode()方法
- DDD中實體與值物件是幹什麼的物件
- equals & hashCode
- java~重寫hashcode和equalsJava
- hashCode()和equals()的區別?(skycto JEEditor)
- java為什麼要重寫hashCode和equals方法Java
- equals&hashCode
- hashCode()與equals()
- Java基礎- ==和equals和hashCode的區別Java
- 關於重寫equals()和hashCode()的思考
- 程式碼安全測試第十七期:物件只定義了Equals和Hashcode方法之一的漏洞物件
- ==、equals、hashcode總結
- 問題:兩個物件值相同(x.equals(y) == true),但是可能存在hashCode不同嗎?物件
- DDD | 03-什麼是實體物件物件
- Java中hashcode和equals效能注意點 - ShaiJavaAI
- 『Java 語法基礎』對 equals() 和 hashCode() 的理解Java
- 關於equals()和hashcode()的一些約定
- “==”、“equals()”、“hashcode()”之間的祕密
- Hashcode相同但是equals不同的例子
- 關於HashMap的key重寫hashcode和equals的理解HashMap
- 用例驅動實現DDD的方法 - codex
- java自定義equals函式和hashCode函式Java函式
- equals與hashCode關係梳理
- Java 物件的雜湊值是每次 hashCode() 方法呼叫重計算麼?Java物件
- 如何正確實現 Java 中的 HashCodeJava
- C#中實現窗體間傳值方法C#
- DDD值物件:被遺忘的價值 – SoftwareMill Tech Blog物件REM
- HashMap的實現原理 HashMap底層實現,hashCode如何對應bucket?HashMap
- ==和equals方法的比較
- ==和equals方法的區別
- 從語言設計的角度探究Java中hashCode()和equals()的關係Java