我有一个包含键(字符串)和值(POJO)的地图。
我想遍历这个地图并更改一些POJO中的数据。
我继承的当前代码删除了给定的条目,并在对POJO进行一些更改后重新添加它。
这不太好,因为您不应该在迭代地图时修改它(方法是同步的,但仍可能出现ConcurrentModificationException)。
我的问题是,如果我需要遍历地图并更改值,那么我可以使用哪些最佳实践/方法来做到这一点?创建一个单独的映射并随着遍历而构建它,然后返回该副本?
我有一个包含键(字符串)和值(POJO)的地图。
我想遍历这个地图并更改一些POJO中的数据。
我继承的当前代码删除了给定的条目,并在对POJO进行一些更改后重新添加它。
这不太好,因为您不应该在迭代地图时修改它(方法是同步的,但仍可能出现ConcurrentModificationException)。
我的问题是,如果我需要遍历地图并更改值,那么我可以使用哪些最佳实践/方法来做到这一点?创建一个单独的映射并随着遍历而构建它,然后返回该副本?
两个选项:
我继承的当前代码会将给定的条目移除,然后在对POJO进行一些更改后将其重新添加回去。
你是否正在更改POJO的引用?例如,使得该条目指向完全不同的东西?如果没有,那么根本没有必要从地图中删除它,你可以直接更改。
如果你确实需要更改POJO的引用(例如,条目的值),你仍然可以通过迭代来就地修改entrySet()
中的Map.Entry
。你可以在条目上使用setValue
,这不会修改你正在遍历的内容。
例如:
Map<String,String> map;
Map.Entry<String,String> entry;
Iterator<Map.Entry<String,String>> it;
// Create the map
map = new HashMap<String,String>();
map.put("one", "uno");
map.put("two", "due");
map.put("three", "tre");
// Iterate through the entries, changing one of them
it = map.entrySet().iterator();
while (it.hasNext())
{
entry = it.next();
System.out.println("Visiting " + entry.getKey());
if (entry.getKey().equals("two"))
{
System.out.println("Modifying it");
entry.setValue("DUE");
}
}
// Show the result
it = map.entrySet().iterator();
while (it.hasNext())
{
entry = it.next();
System.out.println(entry.getKey() + "=" + entry.getValue());
}
输出结果(没有特定顺序)为:
访问two
修改它
访问one
访问three
two=DUE
one=uno
three=tre
...没有任何修改异常。您可能希望在这种情况下对其进行同步,以防其他内容也在查看/更改该条目。
同时迭代一个Map
并添加元素通常会导致大多数Map
类抛出ConcurrentModificationException
异常。而对于不会抛出异常的Map
类(例如ConcurrentHashMap
),不能保证所有条目都能被迭代。
根据你的具体需求,你可以在迭代时执行以下操作:
Iterator.remove()
方法删除当前条目,或者Map.Entry.setValue()
方法修改当前条目的值。对于其他类型的更改,您可能需要执行以下操作:
Map
中的条目创建一个新的Map
,或者Map
上。最后,Google Collections和Apache Commons Collections库具有“转换”地图的实用程序类。
为此,您应该使用地图公开的集合视图:
示例:将所有包含下划线的键的值转换为大写
for(Map.Entry<String, String> entry:map.entrySet()){
if(entry.getKey().contains("_"))
entry.setValue(entry.getValue().toUpperCase());
}
实际上,如果你只想编辑值对象,使用values集合进行操作即可。假设你的映射类型为<String, Object>
:
for(Object o: map.values()){
if(o instanceof MyBean){
((Mybean)o).doStuff();
}
}
Maps.transformValues()
和Maps.transformEntries()
。java.util.concurrent.atomic.AtomicReference
用作您地图的值类型。在您的情况下,这意味着声明您地图的类型。Map<String, AtomicReference<POJO>>
你确实不需要引用的原子特性,但这是一种廉价的方法,可以使值插槽可重新绑定,而无需通过Map#put()
替换整个Map.Entry
。
尽管如此,在阅读了这里的其他回答后,我也建议使用{{link1:Map.Entry#setValue()
}},直到今天我都没有需要或注意到它。
尝试使用 ConcurrentHashMap 。
从JavaDoc:
支持全检索并可调整更新的期望并发性的哈希表。
一般来说,要发生ConcurrentModificationException异常:
一个线程在迭代集合时修改集合是不被允许的。