HashMap keySet changes not reflected in map

I am trying to iterate on HashMap and rewrite some of the elements to the other map, but I have following problem:

@Test
public void test() {
    Map<SubClass, String> map = new HashMap<SubClass,String>();
    Map<SubClass, String> anotherMap = new HashMap<SubClass,String>();
    map.put(new SubClass(), "10");

    for(SubClass i : map.keySet()) {
        System.out.println(i); // initial (because toString is implemented)
        System.out.println(map.get(i)); // 10
        // here it's ok...

        i.name="another";

        System.out.println(i); // another
        System.out.println(map.get(i)); // null!
        // but here it occurs that  map.get(i) returns null!

        anotherMap.put(i, map.get(i));
    }
    for(SubClass i : anotherMap.keySet()) {
        System.out.println(i); // another
        System.out.println(map.get(i)); // null!
    }
}
// SubClass has String name; and hashCode and equals implemented

According to javadoc:

java.util.Map.keySet()

Returns a Set view of the keys contained in this map. The set is backed by the map, so changes to the map are reflected in the set, and vice-versa. If the map is modified while an iteration over the set is in progress (except through the iterator's own remove operation), the results of the iteration are undefined. The set supports element removal, which removes the corresponding mapping from the map, via the Iterator.remove, Set.remove, removeAll, retainAll, and clear operations. It does not support the add or addAll operations.

It says "changes to the map are reflected in the set, and vice-versa". So why does it behave this way and most important: how can I overcome it to make both maps contain only modified key and non-null value?

UPDATE: My friend did this test on java 1.5.0.19 (I have 1.7.0_03, and the same occurs on 1.5.0_21) and got correct output:

initial
10
another
10

UPDATE2: Oh, he didn't implement hashCode/equals, so first update is irrelevant

Answers


You're modifying the key, not the map. There is no way for Map<K,V> to detect that you have changed the object within it. To make the map "see" the change you will need to call remove(originalKey), change the key, then call put(modifiedKey,object).

Modifying the map would be a call to clear, put, putAll, or remove.


Per the java.util.Map javadoc:

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.

In other words, maps aren't required to work correctly if you change the key objects in a way that affects how the keys will be indexed within the map.


Try with something like:

for(SubClass i : map.keySet()) {
    Object old = map.get(i);

    i.name="another";

    anotherMap.put(i, old);
}

I believe i.name = "another" is changing the hashcode of the SubClass i object, that's why you get back a null value. The code will work if the hashcode function of Subclass does not use name.

And so when you call anotherMap.put(i, map.get(i)); you are really calling anotherMap.put(i, null);

The purpose of the HashMap is to perform lookups in O(1) time. That's why it takes the hashcode from SubClass i and does not update it. Your suggestion would require a lazy HashMap, but that would take O(n) time and defeat the purpose.


I'm answering myself, since my friend got better solution than anybody here before:

for (Map.Entry<SubClass, String> entry: map.entrySet()) {
    System.out.println(entry.getKey().name);
    System.out.println(entry.getValue());

    entry.getKey().name = "another";

    System.out.println(entry.getKey().name);
    System.out.println(entry.getValue());
}

this will work :)


Need Your Help

Getting URL including # value

javascript php url dom post

I need to grab the value of URL after #. For example in this URL: http://url.com/index.php#33/1032. I just need to get "33/1032" But PHP seems to not allow any value after #. So I used JS. Here is ...

Is there a MySQL function written to process sign bits like credit card number?

mysql function checksum

I would like to store some numbers (some serial numbers) and add one check sum- just like how a credit card number is having the last digit.