2012-07-05 52 views
1

我想,反覆HashMap并改写一些元素与其它地图,但是我有以下问题:的HashMap的keySet变化不会反映在地图

@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 

据的Javadoc:

java.util.Map.keySet()

返回此映射中包含的键的Set视图。该组由地图支持,因此对地图的更改反映在该组中,反之亦然。如果在对集合的迭代处于 进度(除了通过迭代器自己的删除操作)上迭代迭代后修改映射,则迭代结果为 未定义。该集合支持元素删除,该操作通过Iterator.remove,Set.remove,removeAll,retainAll和clear操作从映射中删除相应的映射,即 。它不支持 add或addAll操作。

它说“地图的变化反映在集合中,反之亦然”。那么为什么它的行为是这样,最重要的:我如何克服它,使两个图只包含修改后的键和非空值?

UPDATE: 我的朋友做了关于Java 1.5.0.19(我有1.7.0_03,同样发生在1.5.0_21)该项测试,并得到了正确的输出:

initial 
10 
another 
10 

UPDATE2: 哦,他没有实现的hashCode /等于,所以第一次更新是无关紧要

+2

你正在改变的关键...这永远不会工作,因为你的第二个呼叫map.get(我)是基于比第一个 – Fido 2012-07-05 15:29:52

+0

这样一个不同的密钥是什么意思javadoc中的这一部分:“更改地图反映在集合中,反之亦然“? – 2012-07-05 15:31:22

+0

集合中的变化与集合中元素的变化不同 – Fido 2012-07-05 15:32:31

回答

4

你修改关键,而不是地图Map<K,V>无法检测到您已更改其中的对象。要使地图“看到”更改,您需要拨打remove(originalKey),更改密钥,然后致电put(modifiedKey,object)

修改地图可以调用clear,put,putAllremove

+0

请查看我的更新。那为什么它对我的朋友有效? – 2012-07-05 15:37:30

+1

也许工作对你的朋友为这个特定的操作,但如果'name'是重点对象的'equals'或'hashCode'实施的一部分,该地图的行为是_undefined_。一般来说,这意味着在实践中,地图的行为是不可预测的:出现在'entrySet'中的条目不会与'map.get(key)'一起出现;您不能删除以这种方式损坏的条目。地图是_corrupted._ – 2012-07-05 16:23:21

+0

@AdamPierzchała我不能说没有看到你的朋友代码。 – 2012-07-05 16:33:27

0

我相信i.name = "another"正在改变SubClass i对象的哈希码,这就是为什么你找回null值。如果Subclasshashcode函数不使用name,代码将工作。

所以,当你调用anotherMap.put(i, map.get(i));你真的叫anotherMap.put(i, null);

HashMap的目的是为O执行查找(1)时间。这就是为什么它需要SubClass i的哈希码,并且不会更新它。你的建议将需要一个懒惰的HashMap,但这将花费O(n)时间并击败目的。

0

尝试的东西,如:

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

    i.name="another"; 

    anotherMap.put(i, old); 
} 
-1

我回答我自己,因为我的朋友之前有比任何人都更好的解决方案在这里:

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()); 
} 

这将工作:)

+1

我怀疑这会起作用。如果'SubClass.equals()'或'hashCode'取决于'name',那么这一切都将做的是腐败的地图,使自己的行为完全不可预测的。您应该___不要修改映射中的关键对象,特别是不要影响它们的equals()或hashCode()。 – 2012-07-05 16:18:17

+0

你检查过了吗?我检查了它并且工作(对于这个简单的例子),为什么/何时不起作用? – 2012-07-09 07:46:38

+0

好的,我读过@Kenster的回答 - 在javadocs中没有注意到这一点。我会改变实施。 – 2012-07-09 07:52:28

3

java.util.Map javadoc

注意:如果可变对象用作映射键,则必须非常小心。如果对象的值以影响等于比较的方式更改,而对象是地图中的关键字,则不会指定地图的行为。

换句话说,如果您更改键对象的方式会影响键在地图中的索引方式,则无需正确地使用地图。