8

EqualsVerifierライブラリを使用して JavaequalshashCode契約することに疑問があります。

このようなものがあると想像してください

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クラスでequalsandhashCode契約を満たしているかどうかをテストしようとしています

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

このテストを実行すると、最終的なメソッドを宣言するequals必要hashCodeがあることがわかりますが、子の属性を使用したいので、拡張クラスでこれら 2 つのメソッドを宣言したい場合があるため、これはやりたくないことです。とequalshashCode

EqualsVerifier ライブラリの最終ルールのテストを飛ばしていただけますか? または、何か不足していますか?

4

2 に答える 2

17

免責事項: 私は EqualsVerifier の作成者です。私はちょうどこの質問を発見しました:)。

Joachim Sauer が言及している回避策は正しいです。

EqualsVerifier があなたの実装を好まない理由を説明しましょう。Person今のところ、それは抽象的ではないふりをしましょう。例が少し簡単になります。次のように、2 つの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

b1Personequalsメソッドが呼び出され、 を無視するため、 は true ですworkDescription。のメソッドが呼び出され、そのメソッドのorチェックが false を返すb2ため、は false です。WorkerequalsinstanceofgetClass()

つまり、equalsメソッドはもはや対称的ではなく、Javadocequalsによると、これは を正しく実装するための要件です。

確かに を使用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

b3とは異なるクラス (正確には と )であるため、b4falseです。は対称になったので、少なくともコントラクトには従いますが、それでもあなたが望むものではありません: のように「動作」しません。技術的に言えば、これはオブジェクト指向プログラミングの基礎であるLiskov Substitution Principleを破っています。person1fetchedPersonPersonPerson$ProxyequalsfetchedPersonPerson

これらすべてを機能させる方法はありますが、かなり複雑です。(本当に知りたい場合は、この記事でその方法を説明します。) 単純にするために、EqualsVerifier はequalsandhashCodeメソッドを final にすることをお勧めします。ほとんどの場合、これで問題なく動作します。本当に必要な場合は、いつでも複雑なルートをたどることができます。

あなたの場合、は抽象的であるため、 に実装せずに(およびその他のサブクラスに)のみ実装するPersonことも選択できます。equalsPersonWorker

于 2014-12-01T21:16:36.547 に答える
4

それを正しくすることは非常に難しいです。

EqualsVerifier のドキュメントには、回避策が説明されています。

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

これが機能するためには、 a が aと等しくならない (または等しくならない)getClass()ため、equalsをチェックインする必要があることに注意してください。WorkerPerson

于 2013-11-12T10:23:06.233 に答える