I have a class Output which basically contains a BitSet with overides on hashCode and equals. Then I have a HashSet of Outputs and I do the following operations :
Set<Output> outputs = new HashSet<>();
Output o1 = new Output();
o1.flip(3);
Output o2 = new Output();
o2.flip(1);
o2.flip(3);
outputs.add(o1);
outputs.add(o2);
If I do a print(outputs) I get
[Output@5a1, Output@5a3]
Now if I do
o2.flip(1);
I get
[Output@5a3, Output@5a3]
which of course, is the normal behavior of a Set, since the Set can't be aware that the hashcode of an element has changed.
If I now do
outputs.remove(o1);
I get
[Output@5a3]
Perfect!
But if I do it again
outputs.remove(o1); //or outputs.remove(o2);
it returns false
and I still have [Output@5a3]
And this is strange since if I do
outputs.contains(o1) -> false
which may explain the remove behavior, altough I don't understand why it returns false since if I do
for(Output o : outputs) {
System.out.println(o.equals(o1));
}
It outputs true
.
Any ideas why this happens?
The complete code:
class Output {
BitSet values;
public Output() {
values = new BitSet(4);
}
public void flip(int index) {
values.flip(index);
}
public int hashCode() {
int hash = 3;
hash = 67 * hash + Objects.hashCode(this.values);
return hash;
}
@Override
public boolean equals(Object obj) {
if (!(obj instanceof Output)) {
return false;
}
Output other = (Output) obj;
return this.values.equals(other.values);
}
}
public class Main {
public static void main(String args[]) {
Set<Output> outputs = new HashSet<>();
Output o1 = new Output();
o1.flip(3);
Output o2 = new Output();
o2.flip(1);
o2.flip(3);
outputs.add(o1);
outputs.add(o2);
System.out.println(outputs);
o2.flip(1);
System.out.println(outputs);
outputs.remove(o1);
System.out.println(outputs);
outputs.remove(o1);
System.out.println(outputs);
for (Output o : outputs) {
System.out.println(o.equals(o1));
}
}
}
Output:
[Output@5a1, Output@5a3]
[Output@5a3, Output@5a3]
[Output@5a3]
[Output@5a3]
true
When you mutate an element of a
HashSet
(or a key in aHashMap
), thehashCode
of that element may change (and in your example, thehashCode
depends on thehashCode
of theBitSet
member, which you changed).However, the
HashSet
is not aware of that change, and therefore doesn't move the element to the bin corresponding with the newhashCode
.Therefore, when you search for that element, the search is performed using the new
hashCode
(HashSet
search always begins with thehashCode
- only after finding the bin that contains all the elements having thathashCode
,equals()
is used to find the right element), and it fails, since the element is still located in the bin matching the originalhashCode
.That's the reason it is a bad idea to mutate elements of a
HashSet
.