Reputation: 21
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?
Upvotes: 1
Views: 397
Reputation: 5578
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();
}
Upvotes: 5