我知道在迭代期间不应该修改集合。所以我们应该有解决方法。
我有一个代码:
Map<Key, Value> map = getMap(); // map generating is hidden
for (Key key : map.keySet()) {
if (isToRemove(key)) {
map.remove(key);
} else {
map.put(key, getNewValue());
}
}
是未定义的行为还是有效的代码?
keySet文档sais表示地图的更改会反映在返回的集合中,反之亦然。这是否意味着以前的代码是不可接受的?
答案 0 :(得分:5)
answer from davidxxx指出地图上的视图集合链接到地图,并且在迭代视图集合时对地图的修改可能是正确的(+1)结果为ConcurrentModificationException
。地图上的视图集合由entrySet
,keySet
和values
方法提供。
因此,原始代码:
Map<Key, Value> map = getMap();
for (Key key : map.keySet()) {
if (isToRemove(key)) {
map.remove(key);
} else {
map.add(key, getNewValue());
}
}
很可能会抛出ConcurrentModificationException
,因为它会在每次迭代过程中修改地图。
如果该视图集合的迭代器支持remove
操作,则可以在迭代视图集合时从地图中删除条目。 HashMap的视图集合的迭代器确实支持这一点。通过使用迭代地图时获得的setValue
实例的Map.Entry
方法来设置特定地图条目(键值对)的值,也可能 ; s entrySet
。因此,可以在一次迭代中执行您想要执行的操作,而无需使用临时映射。这是执行此操作的代码:
Map<Key, Value> map = getMap();
for (var entryIterator = map.entrySet().iterator(); entryIterator.hasNext(); ) {
var entry = entryIterator.next();
if (isToRemove(entry.getKey())) {
entryIterator.remove();
} else {
entry.setValue(getNewValue());
}
}
请注意使用Java 10的var
构造。如果您不在Java 10上,则必须明确写出类型声明:
Map<Key, Value> map = getMap();
for (Iterator<Map.Entry<Key, Value>> entryIterator = map.entrySet().iterator(); entryIterator.hasNext(); ) {
Map.Entry<Key, Value> entry = entryIterator.next();
if (isToRemove(entry.getKey())) {
entryIterator.remove();
} else {
entry.setValue(getNewValue());
}
}
最后,鉴于这是一个中等复杂的地图操作,使用流来完成工作可能会很有成效。请注意,这会创建一个新地图,而不是就地修改现有地图。
import java.util.Map.Entry;
import static java.util.Map.entry; // requires Java 9
Map<Key, Value> result =
getMap().entrySet().stream()
.filter(e -> ! isToRemove(e.getKey()))
.map(e -> entry(e.getKey(), getNewValue()))
.collect(toMap(Entry::getKey, Entry::getValue));
答案 1 :(得分:3)
HashMap.keySet()方法更准确地说明了:
该集由地图支持,因此对地图的更改将反映在中 集合,反之亦然。
这意味着keySet()
返回的元素和Map
的键引用相同的对象。因此,当然更改Set
的任何元素的状态(例如key.setFoo(new Foo());
)将反映在Map键中,反之亦然。
您应该谨慎并防止在keyset()
次迭代期间修改地图:
如果在对集合进行迭代时修改了地图 (除了通过迭代器自己的删除操作),结果 迭代是未定义的
您可以删除地图条目:
该集支持删除元素,删除相应的元素 从地图映射,通过Iterator.remove,Set.remove,removeAll, retainAll,并清除操作。
但您无法在以下位置添加条目:
它不支持add或addAll操作。
总而言之,在keySet()
迭代器使用Set.remove()
或更多时,只需使用Iterator
的{{1}}进行迭代,然后调用keySet
即可从中删除元素地图。
您可以在迭代后使用的临时Map中添加新元素以填充原始Map。
例如:
Iterator.remove()
修改:
请注意,由于您要修改地图的现有条目,因此应使用Stuart Marks answer中所述的Map<Key, Value> map = getMap(); // map generating is hidden
Map<Key, Value> tempMap = new HashMap<>();
for (Iterator<Key> keyIterator = map.keySet().iterator(); keyIterator.hasNext();) {
Key key = keyIterator.next();
if (isToRemove(key)) {
keyIterator.remove();
}
else {
tempMap.put(key, getNewValue());
}
}
map.putAll(tempMap);
。
在其他情况下,需要使用创建新地图的中介Map.EntrySet
或Map
。
答案 2 :(得分:1)
如果您运行代码,则会获得ConcurrentModificationException
。以下是使用迭代器而不是密钥集或等效的Java8 +功能API的方法:
Map<String, Object> bag = new LinkedHashMap<>();
bag.put("Foo", 1);
bag.put("Bar", "Hooray");
// Throws ConcurrentModificationException
for (Map.Entry<String, Object> e : bag.entrySet()) {
if (e.getKey().equals("Foo")) {
bag.remove(e.getKey());
}
}
// Since Java 8
bag.keySet().removeIf(key -> key.equals("Foo"));
// Until Java 7
Iterator<String> it = bag.keySet().iterator();
while (it.hasNext()) {
if (it.next().equals("Bar")) {
it.remove();
}
}