I was just wondering, what would happen if key of a HashMap
is mutable, test program below demonstrate that and I am unable to understand when both equals and hashCode
methods returns
true and same value, why does hashmap.containsKey
return false
.
public class MutableKeyHashMap {
public static void main(String []a){
HashMap<Mutable, String> map = new HashMap<Mutable, String>();
Mutable m1 = new Mutable(5);
map.put(m1, "m1");
Mutable m2 = new Mutable(5);
System.out.println(map.containsKey(m2));
m2.setA(6);
m1.setA(6);
Mutable m3 = map.keySet().iterator().next();
System.out.println(map.containsKey(m2)+" "+m3.hashCode()+" "+m2.hashCode()+" "+m3.equals(m2));
}
}
class Mutable {
int a;
public Mutable(int a) {
this.a = a;
}
@Override
public boolean equals(Object obj) {
Mutable m = (Mutable) obj;
return m.a == this.a ? true : false;
}
@Override
public int hashCode(){
return a;
}
public void setA(int a) {
this.a = a;
}
public int getA() {
return a;
}
}
This the output :
true false 6 6 true
The javadoc explains it
Basically, don't use mutable objects as keys in a Map, you're going to get burnt
To extrapolate, because the docs may not appear clear, I believe the pertinent point here is `changed in a manner that affects equals', and you seem to be assuming that equals(Object) is called each time contains is invoked. The docs don't say that, the wording implies they may be allowed to cache computations.
Looking at the source, it seems that because your hashCode returns a different value (was 5, now 6), it's possible that it's being looked up in a different bucket based on implementation details.
When you put "m1" the first time around,
hashCode()
was 5. Thus theHashMap
used 5 to place the value into the appropriate bucket. After changingm2
, thehashCode()
was 6 so when you tried looking for the value you put in, it the bucket it looked in was different.You can think of if this way, the Map has 16 buckets. When you give it an object with A == 5, it tosses it into bucket 5. Now you can change A to 6, but it's still in bucket 5. The Map doesn't know you changed A, it doesn't rearrange things internally.
Now you come over with another object with A == 6, and you ask the Map if it has one of those. It goes and looks in bucket 6 and says "Nope, nothing there." It's not going to go and check all the other buckets for you.
Obviously how things get put into buckets is more complicated than that, but that's how it works at the core.
The
HashMap
puts your object at the location for hash key5
. Then you change the key to6
and usecontainsKey
to ask the map whether it contains the object. The map looks at position6
and finds nothing, so it answersfalse
.So don't do that, then.
A code example to accompany ptomli's answer.
Compiles and runs it. The result is:
I am using Java 6 on Linux.