Updating Java HashMap key

2019-02-12 18:45发布

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

5条回答
ゆ 、 Hurt°
2楼-- · 2019-02-12 19:22

The javadoc explains it

Note: great care must be exercised if mutable objects are used as map keys. The behavior of a map is not specified if the value of an object is changed in a manner that affects equals comparisons while the object is a key in the map.

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.

查看更多
等我变得足够好
3楼-- · 2019-02-12 19:33

When you put "m1" the first time around, hashCode() was 5. Thus the HashMap used 5 to place the value into the appropriate bucket. After changing m2, the hashCode() was 6 so when you tried looking for the value you put in, it the bucket it looked in was different.

查看更多
4楼-- · 2019-02-12 19:34

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.

查看更多
We Are One
5楼-- · 2019-02-12 19:34

The HashMap puts your object at the location for hash key 5. Then you change the key to 6 and use containsKey to ask the map whether it contains the object. The map looks at position 6 and finds nothing, so it answers false.

So don't do that, then.

查看更多
太酷不给撩
6楼-- · 2019-02-12 19:37

A code example to accompany ptomli's answer.

import java.util.*;

class Elem {
    private int n;

    public Elem(int n) {
        this.n = n;
    }

    public void setN(int n) {
        this.n = n;
    }

    @Override
    public int hashCode() {
        return n;
    }

    @Override
    public boolean equals(Object e) {
        if (this == e)
            return true;
        if (!(e instanceof Elem))
            return false;
        Elem an = (Elem) e;
        return n == an.n;
    }
}

public class MapTest {
    public static void main (String [] args)  {
        Elem e1 = new Elem(1);
        Elem e2 = new Elem(2);

        HashMap map = new HashMap();
        map.put(e1, 100);
        map.put(e2, 200);

        System.out.println("before modification: " + map.get(e1));  
        e1.setN(9);
        System.out.println("after modification using updated key: " + map.get(e1)); 

        Elem e3 = new Elem(1);
        System.out.println("after modification using key which equals to the original key: " + map.get(e3));    
    }
}

Compiles and runs it. The result is:

before modification: 100
after modification using updated key: null
after modification using key which equals to the original key: null

I am using Java 6 on Linux.

查看更多
登录 后发表回答