如何在Java中遍历HashMap并替换其中的值

79

我正在使用一个Runnable定时器,每秒自动从玩家的冷却时间中减去20,但是我不知道如何在迭代过程中替换值的值。如何更新每个键的值?

public class CoolDownTimer implements Runnable {
    @Override
    public void run() {
        for (Long l : playerCooldowns.values()) {
            l = l - 20;
            playerCooldowns.put(Key???, l);
        }
    }

}
4个回答

142

使用Java 8:

map.replaceAll((k, v) -> v - 20);

使用Java 7或更早版本:

您可以按如下方式迭代条目并更新值:

for (Map.Entry<Key, Long> entry : playerCooldowns.entrySet()) {
    entry.setValue(entry.getValue() - 20);
}

4
实际上,这种模式存在一个FindBugs警告:使用keySet迭代器而不是entrySet迭代器效率低下。虽然在大多数情况下这并不重要,但在某些情况下,它的性能差异可能非常大。此外,我认为在这两种情况下代码都很清晰易读。为什么要使用并发布明显有一些缺点且没有任何优势的模式呢?(即使这些缺点可以说相对较小) - Tim Büthe
@aioobe:我可以想象这对不同的映射实现有不同的影响,但是有没有一种情况下前一种迭代形式比后一种更有效?因此FindBugs警告将是误报?但你是对的,我们不必在这里讨论。你的答案还是可以的,我想。 - Tim Büthe
3
为什么这样做可行?据我记得,在使用 for each 循环迭代集合时,不允许更改集合的元素。 - Alex Li
6
不允许进行结构性修改,如添加或删除元素。 - aioobe
如上所述,我认为Java 7部分中的keySet循环是错误的。根据https://docs.oracle.com/javase/8/docs/api/java/util/Map.html#keySet--中指定的,如果更改了映射表,则迭代的行为是未定义的 - 即使键保持不变也可能会使用put进行更改。entrySet示例没有相同的缺陷,因为它直接修改值,这是https://docs.oracle.com/javase/8/docs/api/java/util/Map.html#entrySet--允许的。 - Kasper Nielsen
显示剩余3条评论

29

你不能像现在这样迭代Map中的值(如你正在做的),因为那样你没有引用键,如果你没有引用键,那么你就无法更新Map中的条目,因为你无法找到与刚刚更新的值关联的键。

在使用Map时,你有两个选项来进行此类更新,一个是迭代Map中的每个Map.Entry<K,V>,另一个是迭代键Set。Map上都有相应的方法来实现这两种方式。个人建议使用迭代每个Map.Entry<K,V>的方式。

for (Map.Entry<String, Long> entry : playerCooldowns.entrySet()) {
    entry.setValue(entry.getValue() - 20);
}

11
为什么不迭代Map.Entry对象?每个将给您键和值,您无需对Map执行额外的get()来获取值。

0
如果您尝试向不可修改的映射添加值,则会收到“java.lang.UnsupportedOperationException”错误。 如果您正在使用不可修改的映射,则需要按照以下方式操作。
Map<String, Object> modifiableMap = new HashMap<>(unmodifiableMap);

// Update the value of a key in the new map
modifiableMap.put("new key1", "new value");

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接