
はじめに
Javaを学び始めて少し経つと、ほとんどの開発者が「equals() と hashCode()」の壁にぶつかります。
オブジェクト同士の比較が思った通りに動かない原因の多くが、この2つのメソッドにあります。
最近では Lombok を使って自動生成するケースも増えましたが、仕組みを理解していないと意図せぬバグを生む こともあります。
この記事では、
を実践的にまとめます。
equals/hashCode の基本
equals() と == の違い
Java では == と equals() は、全く異なる動作をします。
| 比較方法 | 比較内容 | 主な用途 |
|---|---|---|
== | 参照の同一性(同じメモリ上のオブジェクトか) | プリミティブ型、シングルトン判定 |
equals() | 値の同一性(内容が等しいか) | String、独自クラスなどの中身比較 |
String s1 = new String("apple");
String s2 = new String("apple");
System.out.println(s1 == s2); // false(別インスタンス)
System.out.println(s1.equals(s2)); // true(中身が同じ)
equals と hashCode の「約束事」
Javaでは次のルールが定められています。
このため、equals をオーバーライドしたら hashCode も必ず実装する必要があります。
実装(基本形)
以下は、nameとageを持つUserクラスです。このクラスにequals()とhashCode()を実装するといかのようになります。
import java.util.Objects;
public class User {
private String name;
private int age;
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof User)) return false;
User user = (User) o;
return age == user.age && Objects.equals(name, user.name);
}
@Override
public int hashCode() {
return Objects.hash(name, age);
}
}

これがいわゆる教科書的な実装だけれども、現場ではこのようなコードを毎度手で書くのは面倒…。ここで登場するのが Lombokです!
Lombok で自動生成する方法
Lombokとは
Lombok(ロムボック) は、Javaの冗長なコードを削減するための アノテーションベースのライブラリ です。
Getter・Setter・コンストラクタ・toString・equals・hashCodeなどを自動生成し、開発者が手で書く手間を大幅に減らしてくれます。

Javaのプロジェクトでは基本的につかわれているライブラリだね。equalsとhashCodeを実装するには、Lombokの@Dataをつけるだけでいいから、コンパクトになるよね。
@Data を使うだけでOK
import lombok.Data;
@Data
public class User {
private String name;
private int age;
}
@Data をつけると、以下のメソッドが自動生成されます。
つまり、equals と hashCode も自動で実装されるということです。
内部的には、次のようなコードが生成されます。
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
User user = (User) o;
return age == user.age && (name != null ? name.equals(user.name) : user.name == null);
}
@Override
public int hashCode() {
int result = name != null ? name.hashCode() : 0;
result = 31 * result + age;
return result;
}
Lombokを使うときの注意点
比較に含めたくないフィールドがある場合
例えば、DBの主キー id だけは比較対象にしたくない場合、@EqualsAndHashCode をカスタマイズします。
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
@EqualsAndHashCode(of = {"email"}) // email だけで同一判定する
public class User {
private Long id;
private String name;
private String email;
}
このように of 属性を使えば、特定のフィールドのみで比較が可能です。
継承クラスでは注意が必要
デフォルトでは、@EqualsAndHashCode は スーパークラスのフィールドを無視します。
継承関係を考慮したい場合は、callSuper = true を指定します。
@EqualsAndHashCode(callSuper = true)
public class PremiumUser extends User {
private int rank;
}
③ 可変オブジェクトは比較対象にしない
equals / hashCode の基準に、変更されるフィールド(例:lastLoginTime など)を含めると、
コレクションのキーとして使った際に破壊的なバグを生む可能性があります。
User u = new User("Alice", 25);
Map<User, String> map = new HashMap<>();
map.put(u, "Tokyo");
u.setName("Bob"); // ← hashCodeが変わる!
System.out.println(map.get(u)); // null(キーが見つからない)
このようなケースでは、不変(immutable)オブジェクトを設計するか、比較対象を限定することが推奨です。
equals/hashCode のテストも書こう
Lombokを使っていても、動作保証のための単体テストは重要です。
@Test
void testEqualsAndHashCode() {
User u1 = new User("Alice", 25);
User u2 = new User("Alice", 25);
assertEquals(u1, u2);
assertEquals(u1.hashCode(), u2.hashCode());
}
Lombokが正しく生成しているか、フィールド変更の影響を確認できます。

あるに越したことはないけれど、@Dataをつけるだけのクラスに対してはテストコードは正直不要かな…。ただof属性をつかった独自実装がある場合はテストを実装すべき!
まとめ
equals() と hashCode() は、Javaの中でも理解の浅さがバグにつながりやすい領域です。
Lombokを使えば簡単に書けますが、その仕組みと制約を理解したうえで使うことで、はじめて「安全な自動化」が実現します。
もしチーム開発でLombokを導入しているなら、レビュー時に「どのフィールドで比較しているか」「可変データを含めていないか」を確認するのがベストプラクティスです。
