【问题标题】:HashMap keySet changes not reflected in mapHashMap keySet 更改未反映在地图中
【发布时间】:2012-07-05 15:25:47
【问题描述】:

我正在尝试迭代 HashMap 并将一些元素重写到另一个地图,但我遇到了以下问题:

@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 视图。该集合由地图支持,因此更改为 map 反映在集合中,反之亦然。如果在对集合进行迭代时修改了地图 进度(通过迭代器自己的删除操作除外),迭代的结果是 不明确的。该集合支持元素移除,即从地图中移除对应的映射, 通过 Iterator.remove、Set.remove、removeAll、retainAll 和 clear 操作。它不支持 add 或 addAll 操作。

它说“对地图的更改会反映在集合中,反之亦然”。那么为什么它会以这种方式表现并且最重要的是:我如何克服它以使两个映射都只包含修改后的键和非空值?

更新: 我的朋友在 java 1.5.0.19 上做了这个测试(我有 1.7.0_03,同样发生在 1.5.0_21)并得到了正确的输出:

initial
10
another
10

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

【问题讨论】:

  • 您正在更改密钥...这永远不会起作用,因为您对 map.get(i) 的第二次调用基于与第一个不同的密钥
  • 那么这部分 javadoc 是什么意思:“对地图的更改反映在集合中,反之亦然”?
  • 集合中的变化与集合中元素的变化不同

标签: java hashmap


【解决方案1】:

您正在修改 key,而不是 mapMap&lt;K,V&gt; 无法检测到您更改了其中的对象。要使地图“看到”更改,您需要调用remove(originalKey),更改密钥,然后调用put(modifiedKey,object)

修改地图将调用clearputputAllremove

【讨论】:

  • 请看我的更新。那为什么它对我的朋友有用呢?
  • 它可能适用于您的朋友进行此特定操作,但如果 name 是关键对象的 equalshashCode 实现的一部分,则地图的行为是 undefined。一般来说,这在实践中意味着地图的行为将无法预测:出现在entrySet 中的条目不会出现在map.get(key) 中;您无法删除以这种方式损坏的条目。地图已损坏。
  • @AdamPierzchała 我不能说没有看到你的朋友代码。
  • 非常感谢,我接受你的回答,因为这是第一个正确的回答。
【解决方案2】:

根据java.util.Map javadoc

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

换句话说,如果您更改键对象的方式会影响键在映射中的索引方式,则映射不需要正常工作。

【讨论】:

    【解决方案3】:

    尝试类似:

    for(SubClass i : map.keySet()) {
        Object old = map.get(i);
    
        i.name="another";
    
        anotherMap.put(i, old);
    }
    

    【讨论】:

      【解决方案4】:

      我相信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) 时间并且无法达到目的。

      【讨论】:

        【解决方案5】:

        我在回答自己,因为我的朋友比以前的任何人都得到了更好的解决方案:

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

        这会起作用:)

        【讨论】:

        • 我怀疑这会奏效。如果SubClass.equals()hashCode 依赖于name,那么所有这些都会破坏地图并使其行为本质上不可预测。您应该切勿修改地图中的关键对象,尤其是不要以影响其equals()hashCode() 的方式。
        • 你检查了吗?我检查了一下,它可以工作(对于这个简单的例子),为什么/什么时候不能工作?
        • 好的,我已经阅读了@Kenster 的答案——之前在 javadocs 中没有注意到这一点。我将更改实现。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2020-09-10
        • 2016-09-26
        • 1970-01-01
        • 2018-08-11
        • 2014-07-12
        • 2015-03-06
        • 1970-01-01
        相关资源
        最近更新 更多