はじめに
Javaでクラスの同値性をチェックする場合、equals()
および hashCode()
を適切に実装する必要があります1。通常は自前で実装しないのでまず間違いはないと思いますが、テストをしないのは不安です。
効率のいい方法がないかと探していたところ、EqualsVerifierというライブラリを見つけました。
インストール
Getting StartedにMavenでの設定方法が書かれています。他のツールの場合はMvnRepositoryを参照してください。
使い方
例えば以下のようなNameクラスがあります。この equals()
と hashCode()
はIntelliJ IDEAで自動生成したものです。
import java.util.Objects;
public final class Name {
private final String name;
public Name of(String name) {
return new Name(name);
}
public Name(String name) {
this.name = name;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Name name1 = (Name) o;
return Objects.equals(name, name1.name);
}
@Override
public int hashCode() {
return Objects.hash(name);
}
}
これに対するテストは次の通りです(JUnit 4.12を使用)。
import nl.jqno.equalsverifier.EqualsVerifier;
import org.junit.Test;
import java.sql.Timestamp;
public final class EqualsVerifierTest {
@Test
public void test_name() {
EqualsVerifier.forClass(Name.class).verify();
}
}
これだけです。簡単ですね。
もちろんこれでは本当にテストしたか分からないので、例えば、equals()
の一般契約に従っていない、Timestampクラスでテストします。
EqualsVerifier.forClass(Timestamp.class).verify();
以下のようなエラーが出ました。
java.lang.AssertionError: Overloaded: More than one equals method found.
Signature should be: public boolean equals(Object obj)
For more information, go to: http://www.jqno.nl/equalsverifier/errormessages
特定のフィールドを除外したい場合
Ignoring fieldsにありますが、例えば、以下のようにして除外できます。詳細はドキュメントを参照してください。
-
withIgnoredFields
メソッドに、無視するフィールド名を渡す - Javaのキーワード
transient
を使う
自分としては transient を使うのがいいかなぁと思います。
動作原理
Why, what, how?の下の方に書かれていますが、だいたい以下の通りです。ちょっと黒魔術ですね。
- モッキングフレームワークと同じように、コンストラクタを呼ばずにインスタンスを生成する。
- リフレクションを使ってフィールドに値を設定する。
- これらに対して
equals()
とhashCode()
が期待される結果になるか確認する。 -
equals()
メソッドのシグネチャを調べ、オーバーロードでなくオーバーライドを使用していることを確認する。
おわりに
楽をしたい気持ちは大切だと思いました( ´ω`)
-
Effective Java 第2版 「項目8 equalsをオーバーライドする時は一般契約に従う」および「項目9 equlsをオーバーライドする時は、常にhashCodeをオーバーライドする」を参照。 ↩