Java:为什么在我们迭代地图时修改地图条目的值是安全的?



假设一个映射包含整数键和一个字符串列表作为其值。然后,我不能这样做:

for (Map.Entry<Integer, List<String>> entry : map.entrySet()){
    for (String string : entry.getValue()){
        if (string.startsWith("a")){
           entry.getValue().remove(string);
        }
    }
}

它抛出ConcurrentModificationException.但是如果我执行以下操作:

for (Map.Entry<Integer, List<String>> entry : map.entrySet()){
    entry.setValue(new ArrayList<String>());
}

这非常有效。我们现在不是在修改底层地图吗?

问题与

Map无关,只与您使用值列表的方式有关。 任何ArrayList,以下内容都将失败:

for (String string : list){
    if (string.startsWith("a")){
       list.remove(string);
    }
}

其原因在ArrayList的Javadoc中进行了讨论:

此类的迭代

器和 listIterator 方法返回的迭代器是快速失败的:如果在创建迭代器后的任何时间对列表进行结构修改,则除了通过迭代器自己的 remove 或 add 方法之外,迭代器将抛出 ConcurrentModificationException。因此,面对并发修改,迭代器会快速而干净地失败,而不是冒着在未来不确定的时间出现任意、非确定性行为的风险。

(换句话说:如果从列表中删除该元素,迭代器可能不再指向底层数组中的正确索引。 因此,它不是允许您使用可能损坏的迭代器,而是出于礼貌抛出一个ConcurrentModificationException,让您知道您需要重新设计程序。

一个简单的解决方法是使用

Iterator<String> itr = entry.getValue().iterator();
while (itr.hasNext()) {
  if (itr.next().startsWith("a")) {
    itr.remove();
  }
}

请查看 Javadoc 中的 HashMap 中的 entrySet(.java 你会发现为什么!

从文档中:

返回此映射中包含的映射的"设置"视图。该集由地图支持,因此对地图的更改将反映在集中,反之亦然。如果在对集合进行迭代时修改了映射(除非通过迭代器自己的删除操作,或通过迭代器返回的映射条目的 setValue 操作),则迭代的结果是未定义的。该集合支持元素删除,通过 Iterator.remove、Set.remove、removeAll、retainAll 和 clear 操作从映射中删除相应的映射。它不支持添加或全部添加操作。

指定者:Map中的entrySet(),

覆盖:AbstractMap 中的 entrySet()

返回:此映射中包含的映射的集合视图