0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Effective Java 3-11:equalsをオーバーライドするときは、常にhashCodeをオーバーライドする

0
Posted at

3-11:equalsをオーバーライドするときは、常にhashCodeをオーバーライドする

要点

equals をオーバーライドしたら、必ず hashCode もオーバーライドする。
等価なオブジェクトは 同じ hashCode() を返さなければならない(これが hashCode の契約)。
守らないと HashMap/HashSet 等のハッシュベースのコレクションが正しく動きません。

なぜか

HashMap や HashSet は内部でハッシュ値(hashCode())を使ってオブジェクトの格納場所(バケット)を決める。

  • equals(a,b) が true でも a.hashCode() != b.hashCode() だと、別バケットになり contains/get が失敗することがある。
    → 等価なのに見つからない、というバグの原因になる。
     

悪い例(問題を再現するコード)

// equals はオーバーライドしたが hashCode をオーバーライドしていない(Object#hashCode のまま)
public class BadPerson {
    private final String name;
    private final int age;
    public BadPerson(String name, int age) { this.name = name; this.age = age; }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof BadPerson)) return false;
        BadPerson p = (BadPerson) o;
        return age == p.age && Objects.equals(name, p.name);
    }
    // hashCode はオーバーライドしていない!
}

// → 使用例
BadPerson a = new BadPerson("Alice", 30);
BadPerson b = new BadPerson("Alice", 30);
Set<BadPerson> set = new HashSet<>();
set.add(a);
set.contains(b); // 期待: true だが、false になる可能性が高い

説明:
a.equals(b) は true でも、a.hashCode() と b.hashCode() が Object の実装(オブジェクト識別に依存)で異なるため、HashSet では別扱いになる。
 

良い例

public class Person {
    private final String name;
    private final int age;
    public Person(String name, int age) { this.name = name; this.age = age; }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof Person)) return false;
        Person other = (Person)o;
        return age == other.age && Objects.equals(name, other.name);
    }

    @Override
    public int hashCode() {
        // シンプルで安全:null 護衛もしてくれる
        return Objects.hash(name, age);
    }
}

これで new Person("A",1).equals(new Person("A",1)) == true のとき、両者は同じ hashCode() を返すためハッシュコレクションは正しく動く。

実務上の注意点(チェックリスト)

  • 等しい → hashCode は同じ を常に保証する。逆は不要(異なるオブジェクトが同じハッシュになることは許容される)。

  • equals に使うフィールドは hashCode にも含める(両方に同じフィールドセットを使う)。

  • ミュータブルフィールドを避ける:equals/hashCode にミュータブルなフィールドを使うと、コレクションに入れた後に状態が変わり見つけられなくなる(バグ)。可能なら不変クラスにする。

  • パフォーマンス:Objects.hash は簡便だがやや遅い(小さなホットパスなら手書きの 31×方式が速い)。必要なら hashCode をキャッシュ(不変クラスのみ)する。

  • 配列フィールドは Arrays.hashCode / Arrays.deepHashCode を使う(Objects.hash に配列を渡すと配列参照のハッシュになる)。

  • 浮動小数点は Double.hashCode / Float.hashCode を使う(== や Double.doubleToLongBits の扱いに注意)。

 
例:配列・double を含む安全な実装

public final class Item {
    private final String name;
    private final double value;
    private final int[] tags;

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof Item)) return false;
        Item other = (Item)o;
        return Double.compare(value, other.value) == 0
            && Objects.equals(name, other.name)
            && Arrays.equals(tags, other.tags);
    }

    @Override
    public int hashCode() {
        int result = Objects.hash(name, Double.hashCode(value));
        result = 31 * result + Arrays.hashCode(tags);
        return result;
    }
}
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?