Are mutable hashmap keys a dangerous practice?

后端 未结 7 1189
鱼传尺愫
鱼传尺愫 2020-11-22 06:58

Is it bad practice to use mutable objects as Hashmap keys? What happens when you try to retrieve a value from a Hashmap using a key that has been modified enough to change

相关标签:
7条回答
  • 2020-11-22 07:40

    Hash maps use hash code and equality comparisons to identify a certain key-value pair with a given key. If the has map keeps the key as a reference to the mutable object, it would work in the cases where the same instance is used to retrieve the value. Consider however, the following case:

    T keyOne = ...;
    T keyTwo = ...;
    
    // At this point keyOne and keyTwo are different instances and 
    // keyOne.equals(keyTwo) is true.
    
    HashMap myMap = new HashMap();
    
    myMap.push(keyOne, "Hello");
    
    String s1 = (String) myMap.get(keyOne); // s1 is "Hello"
    String s2 = (String) myMap.get(keyTwo); // s2 is "Hello" 
                                            // because keyOne equals keyTwo
    
    mutate(keyOne);
    
    s1 = myMap.get(keyOne); // returns "Hello"
    s2 = myMap.get(keyTwo); // not found
    

    The above is true if the key is stored as a reference. In Java usually this is the case. In .NET for instance, if the key is a value type (always passed by value), the result will be different:

    T keyOne = ...;
    T keyTwo = ...;
    
    // At this point keyOne and keyTwo are different instances 
    // and keyOne.equals(keyTwo) is true.
    
    Dictionary myMap = new Dictionary();
    
    myMap.Add(keyOne, "Hello");
    
    String s1 = (String) myMap[keyOne]; // s1 is "Hello"
    String s2 = (String) myMap[keyTwo]; // s2 is "Hello"
                                        // because keyOne equals keyTwo
    
    mutate(keyOne);
    
    s1 = myMap[keyOne]; // not found
    s2 = myMap[keyTwo]; // returns "Hello"
    

    Other technologies might have other different behaviors. However, almost all of them would come to a situation where the result of using mutable keys is not deterministic, which is very very bad situation in an application - a hard to debug and even harder to understand.

    0 讨论(0)
  • 2020-11-22 07:41

    If key’s hash code changes after the key-value pair (Entry) is stored in HashMap, the map will not be able to retrieve the Entry.

    Key’s hashcode can change if the key object is mutable. Mutable keys in HahsMap can result in data loss.

    0 讨论(0)
  • 2020-11-22 07:44

    As others explained, it is dangerous.

    A way to avoid that is to have a const field giving explicitly the hash in your mutable objects (so you would hash on their "identity", not their "state"). You might even initialize that hash field more or less randomly.

    Another trick would be to use the address, e.g. (intptr_t) reinterpret_cast<void*>(this) as a basis for hash.

    In all cases, you have to give up hashing the changing state of the object.

    0 讨论(0)
  • 2020-11-22 07:48

    It has been noted by many well respected developers such as Brian Goetz and Josh Bloch that :

    If an object’s hashCode() value can change based on its state, then we must be careful when using such objects as keys in hash-based collections to ensure that we don’t allow their state to change when they are being used as hash keys. All hash-based collections assume that an object’s hash value does not change while it is in use as a key in the collection. If a key’s hash code were to change while it was in a collection, some unpredictable and confusing consequences could follow. This is usually not a problem in practice — it is not common practice to use a mutable object like a List as a key in a HashMap.

    0 讨论(0)
  • 2020-11-22 07:48

    This will not work. You are changing the key value, so you are basically throwing it away. Its like creating a real life key and lock, and then changing the key and trying to put it back in the lock.

    0 讨论(0)
  • 2020-11-22 07:50

    This is not safe or advisable. The value mapped to by key1 can never be retrieved. When doing a retrieval, most hash maps will do something like

    Object get(Object key) {
        int hash = key.hashCode();
        //simplified, ignores hash collisions,
        Entry entry = getEntry(hash);
        if(entry != null && entry.getKey().equals(key)) {
            return entry.getValue();
        }
        return null;
    }
    

    In this example, key1.hashcode() now points to the wrong bucket of the hash table, and you will not be able to retrieve value1 with key1.

    If you had done something like,

    Key key1 = new Key(0, 0);
    map.put(key1, value1);
    key1.setA(5);
    Key key2 = new Key(0, 0);
    map.get(key2);
    

    This will also not retrieve value1, as key1 and key2 are no longer equal, so this check

        if(entry != null && entry.getKey().equals(key)) 
    

    will fail.

    0 讨论(0)
提交回复
热议问题