1
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?

More than 3 years have passed since last update.

hashCode が同じはずの自作クラスが HashSet 内で重複してしまう

Last updated at Posted at 2021-05-06

TL;DR

HashSet で自作クラスを使用する場合、 hashCode メソッドだけでなく equals メソッドも準備する必要があります。

確認した JDK

openjdk version "1.8.0_265"
OpenJDK Runtime Environment (AdoptOpenJDK)(build 1.8.0_265-b01)
OpenJDK 64-Bit Server VM (AdoptOpenJDK)(build 25.265-b01, mixed mode)

動かしてみる

次のように、各プロパティフィールドが同じ値を持つ場合は hashCode() の結果が同じになるようにしつつ、 equals は true とならないクラスを準備します。

public class MyModel {
    private String name;
    private int age;

    public MyModel(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public boolean equals(Object o) {
        return false; // ちょうどよいサンプル書くのめんどうなので無理やり false します。
    }

    @Override
    public int hashCode() {
        int result = name != null ? name.hashCode() : 0;
        result = 31 * result + age;
        return result;
    }
}

import org.junit.Test;

import java.util.HashSet;

import static org.junit.Assert.*;

public class MyModelTest {

    @Test
    public void testHashSet() {
        HashSet<MyModel> set = new HashSet<>();

        // 同じプロパティを持つオブジェクトを2つ放り込む
        set.add(new MyModel("taro", 10));
        set.add(new MyModel("taro", 10));

        // hashCode が同じなのでサイズは 1 つになるはず
        assertEquals(1, set.size());
    }

}

しかし結果はこうなります。

java.lang.AssertionError: 
Expected :1
Actual   :2

なので hashCode と同じ挙動になるように equals をちゃんと実装すると、サイズは1になります。

public class MyModel {
    private String name;
    private int age;

    public MyModel(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        MyModel myModel = (MyModel) o;

        if (age != myModel.age) return false;
        return name != null ? name.equals(myModel.name) : myModel.name == null;
    }

    @Override
    public int hashCode() {
        int result = name != null ? name.hashCode() : 0;
        result = 31 * result + age;
        return result;
    }
}

なぜ

Java SE 上ではあくまで「このセット内に、(e==null ? e2==null : e.equals(e2)) となる要素e2がない場合は、指定された要素eをこのセットに追加します。」というように、 hashCode でなく equals となってるようです。 "Hash"Set なのに…

OpenJDK では HashSet は内部で HashMap を使用しており、セットする値は HashMap の key として取り扱っています。
なので HashMap の put の実装に依存するわけですね。
このあたりです。

  • HashSet::add

  • HashMap::putVal

1
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
1
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?