我正在迭代,并修改地图(从现有的枚举对象组创建),如下所示:
public class Dispenser {
private Map<Ingredient, Integer> availableIngredients =
new EnumMap<Ingredient, Integer>(Ingredient.class);
public void orderSandwich(SandwichType sandwichType) {
Map<Ingredient, Integer> buffer =
new EnumMap<Ingredient, Integer>(availableIngredients);
for (Map.Entry<Ingredient, Integer> entry :
sandwichType.getIngredients().entrySet()) {
Integer currentUnits = buffer.get(entry.getKey());
buffer.put(entry.getKey(), currentUnits - entry.getValue());
}
availableIngredients.clear();
availableIngredients.putAll(buffer);
}
}
我想询问在这种情况下是否需要临时的,方法本地的,缓冲区集合。我的意思是,它工作正常,但不确定它的好处。我必须清除我的原始集合并将其替换为缓冲区集合的内容,这基本上是在循环中修改的实际映射。
因为它在没有缓冲区集合的情况下工作正常(仅使用我的原始集合),我想知道是否建议采用一种方法而不是其他方法。
非常感谢有关此方面的最佳做法的任何建议。
答案 0 :(得分:1)
这就像避免ConcurrentModificationException s。
在迭代时不能修改集合,否则会抛出此类异常。你发布的是一个非常罕见的习惯用法 - 以这种或那种方式获取集合的副本,然后你可以在修改另一个时迭代一个。
即使在单线程代码中也会发生这种情况 - 例如,类似这样的事情会在具有至少两个元素的集合上抛出该异常:
for (Object o : myCollection)
{
myCollection.remove(o);
}
另一种可能更高效的方法是明确声明Iterator(而不是使用 foreach 循环),然后使用Iterator的remove方法,如果合适的话。 (但这不适用于您的情况,因为您正在重新映射而不是删除元素。)
编辑:虽然在反思时,availableIngredients
地图没有被循环,所以可以直接修改。事实证明你是对的。 :-)可能这是前重构的遗留问题,但可以用
public void orderSandwich(SandwichType sandwichType) {
for (Map.Entry<Ingredient, Integer> entry :
sandwichType.getIngredients().entrySet()) {
Integer currentUnits = availableIngredients.get(entry.getKey());
availableIngredients.put(entry.getKey(), currentUnits - entry.getValue());
}
}
正如你无疑期待的那样。
我想到的一个想法是,通过减少冲突更新的窗口,这可能也是一种错误的尝试,使并发问题“不太可能”。但是,误导是因为线程安全是绝对的;使展示数据竞赛的可能性降低十倍并不是一个很好的时间投入。它仍将失败,“随机”,因此是不正确的。
答案 1 :(得分:1)
您不需要buffer
地图。您可以改为availableIngredients
。一切都会好起来的。没有任何无用的开销。
答案 2 :(得分:0)
在这种情况下,您不必使用缓冲区,因为在修改sandwichType.getIngredients()
时迭代集合availableIngredients
,这些是单独的集合。
对于使用缓冲区是个好主意的情况,可以更容易地替换原始文件:
availableIngredients = buffer;
无需更新原始集合,从现在开始使用新版本就足够了。