简体   繁体   中英

HashSet inserts 2 elements which are equal

I was recently working on a basic task which involved a set and I stumbled upon a curious problem. I have the following class:

public static class Quadruple {
    int a;
    int b;
    int c;
    int d;
    Map<Integer, Integer> histogram;

    public Quadruple(int a, int b, int c, int d) {
        this.a = a;
        this.b = b;
        this.c = c;
        this.d = d;
        this.histogram = new HashMap<>();
        histogram.put(a, histogram.get(a) == null ? 1 : histogram.get(a) + 1);
        histogram.put(b, histogram.get(b) == null ? 1 : histogram.get(b) + 1);
        histogram.put(c, histogram.get(c) == null ? 1 : histogram.get(c) + 1);
        histogram.put(d, histogram.get(d) == null ? 1 : histogram.get(d) + 1);
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }
        if (!(obj instanceof Quadruple)) {
            return false;
        }
        Quadruple q = (Quadruple) obj;
        return q.histogram.equals(this.histogram);
    }

    @Override
    public int hashCode() {
        return Objects.hash(a, b, c, d);
    }

When I initialize 2 objects of this type like so:

Quadruple q1 = new Quadruple(1, 1, 1, 2);
Quadruple q2 = new Quadruple(1, 1, 2, 1);

q1.equals(q2) returns true but both objects can be added separately to a HashSet. Now I understand from the contract of HashSet, that if the object you are trying to add equals() an already present object, it should be considered present and nothing should be done. I've managed to circumvent this issue by using LinkedList and checking if the list contains() the object before adding it, which seems to work accordingly.

My question is, is this behavior normal, as I checked the underlying implementation and saw that the HashMap which is used in HashSet actually checks the values with equals(). Is there something I might be missing?

Your equals method compares histogram , but your hashCode computes the hash from 4 other fields instead. Your implementation of hashCode method violates the contract between equals and hashCode , which says that if two object are equal, they have to have the same hash.

If you look at the implementation of Objects.hash you will get to this code:

public static int hashCode(Object a[]) {
    if (a == null)
        return 0;

    int result = 1;

    for (Object element : a)
        result = 31 * result + (element == null ? 0 : element.hashCode());

    return result;
}

And as you can see in the loop, the order of arguments passed to Object.hash matters.

As for the solution, I can't really see a reason to have fields other than histogram at all. Either way, given the implementation of your equals method, your hashCode method should look like this:

@Override
public int hashCode() {
    return histogram.hashCode();
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM