簡體   English   中英

Equals和hashCode與EqualsVerifier簽訂合同

[英]Equals and hashCode contract with EqualsVerifier

我對使用EqualsVerifier庫的Java中的equalshashCode契約有些懷疑。

想象一下,我們有類似的東西

public abstract class Person {

    protected String name;

    @Override
    public boolean equals(Object obj) {
        // only name is taken into account
    }

    @Override
    public int hashCode() {
        // only name is taken into account
    }

}

以下擴展課程:

public final class Worker extends Person {

    private String workDescription;

    @Override
    public final boolean equals(Object obj) {
        // name and workDescription are taken into account
    }

    @Override
    public final int hashCode() {
        // name and workDescription are taken into account
    }

}

我嘗試使用EqualsVerifier來測試我是否在Person類中實現了equalshashCode契約

    @Test
    public void testEqualsAndHashCodeContract() {
        EqualsVerifier.forClass(Person.class).verify();
    }

運行這個測試,我得到我必須聲明equalshashCode方法final,但這是我不想做的事情,因為我可能想在擴展類中聲明這兩個方法,因為我想使用一些child的屬性在equalshashCode

你可以跳過測試EqualsVerifier庫中的最終規則嗎? 或者我錯過了什么?

免責聲明:我是EqualsVerifier的創建者。 我才發現這個問題:)。

Joachim Sauer提到的解決方法是正確的。

讓我解釋為什么EqualsVerifier不喜歡你的實現。 讓我們假裝現在Person不是抽象的; 它使示例更簡單一些。 假設我們有兩個Person對象,如下所示:

Person person1 = new Person("John");
Person person2 = new Worker("John", "CEO of the world");

讓我們在這兩個對象上調用equals

boolean b1 = person1.equals(person2); // returns true
boolean b2 = person2.equals(person1); // returns false

b1是真的,因為調用了Personequals方法,它忽略了workDescription b2為false,因為調用了Workerequals方法,並且該方法中的instanceofgetClass()檢查返回false。

換句話說,根據Javadoc ,你的equals方法不再是對稱的,這是正確實現equals的要求。

你確實可以使用getClass()來解決這個問題,但是你遇到了另一個問題。 假設您使用Hibernate或模擬框架。 這些框架使用字節碼操作來創建類的子類。 基本上,你會得到這樣一個類:

class Person$Proxy extends Person { }

所以假設您要往返數據庫,如下所示:

Person person1 = new Person("John");
em.persist(person1);
// ...
Person fetchedPerson = em.find(Person.class, "John");

現在,讓我們呼喚equals

boolean b3 = person1.equals(fetchedPerson); // returns false
boolean b4 = fetchedPerson.equals(person1); // also returns false

b3b4是假的,因為person1fetchedPerson屬於不同的類( fetchedPersonPersonPerson$Proxy )。 equals是對稱的,現在,所以至少要遵循合同,但它仍然不是你想要什么: fetchedPerson不“行為”就像一個Person了。 在技​​術術語中:這打破了Liskov替換原則 ,這是面向對象編程的基礎。

有一種方法可以完成所有這些工作,但它非常復雜。 (如果你真的想知道: 這篇文章解釋了如何。)為了簡單起見,EqualsVerifier建議你使你的equalshashCode方法最終。 在大多數情況下,這將工作正常。 如果你真的需要,你可以隨時采取復雜的路線。

在您的情況下,由於Person是抽象的,您也可以選擇不在Person實現equals ,而只在Worker (以及您可能擁有的任何其他子類)中實現equals

做到這一點非常棘手。

EqualsVerifier的文檔解釋了一種解決方法:

EqualsVerifier.forClass(MyClass.class)
    .withRedefinedSubclass(SomeSubclass.class)
    .verify();

請注意,要使其工作,您可能需要檢查equals中的getClass() ,因為Worker可以(或應該)永遠不等於Person

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM