Java

JavaのequalsとhashCode入門

AIによる要約

equalsはオブジェクトの同一性を判断する重要なメソッドですが、hashCodeとセットで考えないとHashSetHashMapで不具合につながります。この記事では、初学者がつまずきやすい理由を現場目線で整理します。

新人SE
新人SE
equalsだけちゃんと書けば、同じ値として扱われるんじゃないんですか?
ポンコツSE
ポンコツSE
Listで見ると動いたように見えることがあります。でもHashSetやHashMapではhashCodeも使われるので、equalsだけだと壊れます。

前回の文字列比較では、==ではなくequalsを使う話をしました。

次に現場で出てくるのが、自分で作ったクラスのequalshashCodeです。

この記事のポイント

  • equalsは「同じ値として扱うか」を決める
  • hashCodeはHashSetやHashMapの探索に使われる
  • equalsを上書きするならhashCodeも合わせて実装する
  • ID値を持つクラスやキー用クラスではレビューされやすい

equalsだけでは足りない理由

HashSetHashMapは、まずhashCodeで大まかな置き場所を決め、その後にequalsで同じかどうかを確認します。

つまり、equalsでは同じなのにhashCodeが違うと、同じ値なのに別物として扱われる可能性があります。

基本の実装例

import java.util.Objects;

public class UserId {
    private final String value;

    public UserId(String value) {
        this.value = Objects.requireNonNull(value);
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }
        if (!(obj instanceof UserId other)) {
            return false;
        }
        return Objects.equals(this.value, other.value);
    }

    @Override
    public int hashCode() {
        return Objects.hash(value);
    }
}

ポイントは、比較対象に使ったフィールドと、ハッシュ値に使うフィールドを揃えることです。

現場ではどんなクラスで必要になるか

  • ユーザーID、商品コード、注文番号などの値オブジェクト
  • Setに入れて重複排除したいクラス
  • Mapのキーとして使うクラス
  • テストでオブジェクト同士を比較したいクラス

ただし、すべてのクラスに必ず必要というわけではありません。DTOやEntityでは、プロジェクトの方針やフレームワークの扱いも確認します。

レビューで見られるポイント

equals/hashCodeの確認リスト

  • equalshashCodeを片方だけ実装していないか
  • 比較に使うフィールドが妥当か
  • 変更される値をMapのキーに使っていないか
  • IDE生成コードやLombok任せにせず、意味を理解しているか

なぜHashSetで急に問題になるのか

equalshashCode の問題は、普通の画面表示では気づきにくいです。ところが、重複排除やMapのキーとして使った瞬間に不具合として表に出ます。

Set<UserId> userIds = new HashSet<>();
userIds.add(new UserId("U001"));
userIds.add(new UserId("U001"));

System.out.println(userIds.size()); // 期待は1件。でも実装次第で2件になる

業務システムでは、注文番号、商品コード、ユーザーIDなどをSetで重複排除する場面があります。equals だけ実装して hashCode がズレていると、同じIDなのに重複して扱われる可能性があります。

レビューで確認される実装の一貫性

レビューでは、単にメソッドがあるかではなく、equalshashCode が同じフィールドを基準にしているかを見ます。

@Override
public boolean equals(Object obj) {
    if (!(obj instanceof UserId other)) {
        return false;
    }
    return Objects.equals(this.value, other.value);
}

@Override
public int hashCode() {
    return Objects.hash(value);
}
// レビューコメント例
// equalsではvalueを見ていますが、hashCodeでは別フィールドを使っています。
// HashSetやHashMapで同一判定が崩れるので、同じ基準に揃えてください。

equals/hashCodeを確認する場面

  • Setで重複排除するクラス
  • Mapのキーに使うクラス
  • IDやコード値を表す値オブジェクト
  • テストでオブジェクト同士を比較するクラス

まとめ

equalshashCodeは、コレクションと一緒に使うと重要度が一気に上がります。自分で値の同一性を決めるクラスでは、必ずセットで考えましょう。

-Java
-, , , ,