Duplicate HashSet entries with original class

kojisaiki

Koji Saiki

Posted on May 6, 2021

Duplicate HashSet entries with original class

TL;DR

You should implement both of hashCode and equals for using in HashSet.

Env

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)
Enter fullscreen mode Exit fullscreen mode

Try

Below MyModel class is my sample class with some properties and provide same hashcode with same property values.
However equals returns false.

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;
    }

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

Enter fullscreen mode Exit fullscreen mode
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<>();

        // Add models with same property values.
        set.add(new MyModel("taro", 10));
        set.add(new MyModel("taro", 10));

        // We want the set has only 1 entry.
        assertEquals(1, set.size());
    }

}
Enter fullscreen mode Exit fullscreen mode

But, you will see the result as below.

java.lang.AssertionError: 
Expected :1
Actual   :2
Enter fullscreen mode Exit fullscreen mode

For pass the assertion, you should implement equals also.

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;
    }
}

Enter fullscreen mode Exit fullscreen mode

Why

In OpenJDK, HashSet depends on HashMap implementation.

  • HashSet::add

https://github.com/openjdk/jdk/blob/jdk8-b120/jdk/src/share/classes/java/util/HashSet.java#L219

  • HashMap::putVal

https://github.com/openjdk/jdk/blob/jdk8-b120/jdk/src/share/classes/java/util/HashMap.java#L633-L634

💖 💪 🙅 🚩
kojisaiki
Koji Saiki

Posted on May 6, 2021

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related