1. 概述
本文将介绍更新 HashMap 中指定键对应值的不同方法。首先,我们回顾 Java 8 之前的传统解决方案;然后,探索 Java 8 及更高版本新增的便捷方法。
2. 初始化示例 HashMap
为演示 HashMap 的值更新操作,我们创建一个以水果为键、价格为值的 Map:
Map<String, Double> priceMap = new HashMap<>();
priceMap.put("apple", 2.45);
priceMap.put("grapes", 1.22);
后续示例将基于这个 Map 操作。现在让我们深入了解 HashMap 键值更新的各种方法。
3. Java 8 之前的方案
3.1. put 方法
put 方法既能更新值也能新增条目:若键已存在则更新值,否则新增键值对。
通过两个测试验证其行为:
@Test
public void givenFruitMap_whenPuttingAList_thenHashMapUpdatesAndInsertsValues() {
Double newValue = 2.11;
fruitMap.put("apple", newValue);
fruitMap.put("orange", newValue);
Assertions.assertEquals(newValue, fruitMap.get("apple"));
Assertions.assertTrue(fruitMap.containsKey("orange"));
Assertions.assertEquals(newValue, fruitMap.get("orange"));
}
- 键
apple
已存在 → 值被更新(第一个断言通过) - 键
orange
不存在 → 新增条目(后两个断言通过)
3.2. containsKey + put 组合
通过 containsKey 检查键是否存在,再决定是否调用 put 更新值。若键不存在,可选择新增或跳过。
测试用例如下:
@Test
public void givenFruitMap_whenKeyExists_thenValuesUpdated() {
double newValue = 2.31;
if (fruitMap.containsKey("apple")) {
fruitMap.put("apple", newValue);
}
Assertions.assertEquals(Double.valueOf(newValue), fruitMap.get("apple"));
}
因 apple
存在,containsKey 返回 true → put 执行更新 → 断言通过。
4. Java 8+ 新特性
4.1. replace 方法
Map 接口提供了两个重载的 replace 方法:
public V replace(K key, V value);
public boolean replace(K key, V oldValue, V newValue);
单参数版本
仅接收键和新值,返回旧值:
@Test
public void givenFruitMap_whenReplacingOldValue_thenNewValueSet() {
double newPrice = 3.22;
Double applePrice = fruitMap.get("apple");
Double oldValue = fruitMap.replace("apple", newPrice);
Assertions.assertNotNull(oldValue);
Assertions.assertEquals(oldValue, applePrice);
Assertions.assertEquals(Double.valueOf(newPrice), fruitMap.get("apple"));
}
⚠️ 踩坑注意:若键不存在会返回 null,这可能导致歧义——无法区分是键不存在还是值为 null。
三参数版本
接收键、旧值和新值,仅当当前值匹配旧值时才更新,返回布尔值表示是否成功:
@Test
public void givenFruitMap_whenReplacingWithRealOldValue_thenNewValueSet() {
double newPrice = 3.22;
Double applePrice = fruitMap.get("apple");
boolean isUpdated = fruitMap.replace("apple", applePrice, newPrice);
Assertions.assertTrue(isUpdated);
}
@Test
public void givenFruitMap_whenReplacingWithWrongOldValue_thenNewValueNotSet() {
double newPrice = 3.22;
boolean isUpdated = fruitMap.replace("apple", Double.valueOf(0), newPrice);
Assertions.assertFalse(isUpdated);
}
- 第一个测试:旧值匹配 → 更新成功
- 第二个测试:旧值不匹配 → 更新失败
4.2. getOrDefault + put 组合
当键不存在时,getOrDefault 提供默认值,配合 put 可安全新增条目,避免 NullPointerException:
@Test
public void givenFruitMap_whenGetOrDefaultUsedWithPut_thenNewEntriesAdded() {
fruitMap.put("plum", fruitMap.getOrDefault("plum", 2.41));
Assertions.assertTrue(fruitMap.containsKey("plum"));
Assertions.assertEquals(Double.valueOf(2.41), fruitMap.get("plum"));
}
键 plum
不存在 → getOrDefault 返回默认值 2.41 → put 新增条目。
4.3. putIfAbsent 方法
功能等同于 getOrDefault + put 组合:
- 键不存在 → 新增条目
- 键存在 → 不更新值(例外:若原值为 null 则会更新)
测试用例:
@Test
public void givenFruitMap_whenPutIfAbsentUsed_thenNewEntriesAdded() {
double newValue = 1.78;
fruitMap.putIfAbsent("apple", newValue);
fruitMap.putIfAbsent("pear", newValue);
Assertions.assertTrue(fruitMap.containsKey("pear"));
Assertions.assertNotEquals(Double.valueOf(newValue), fruitMap.get("apple"));
Assertions.assertEquals(Double.valueOf(newValue), fruitMap.get("pear"));
}
apple
已存在 → 值不变pear
不存在 → 新增条目
4.4. compute 方法
基于 BiFunction 更新值:若键存在则应用函数计算新值,否则抛出 NullPointerException:
@Test
public void givenFruitMap_whenComputeUsed_thenValueUpdated() {
double oldPrice = fruitMap.get("apple");
BiFunction<Double, Integer, Double> powFunction = (x1, x2) -> Math.pow(x1, x2);
fruitMap.compute("apple", (k, v) -> powFunction.apply(v, 2));
Assertions.assertEquals(
Double.valueOf(Math.pow(oldPrice, 2)), fruitMap.get("apple"));
Assertions.assertThrows(
NullPointerException.class, () -> fruitMap.compute("blueberry", (k, v) -> powFunction.apply(v, 2)));
}
apple
存在 → 值更新为平方blueberry
不存在 → 抛出 NPE
4.5. computeIfAbsent 方法
键不存在时新增条目:通过函数计算新值,避免 NPE:
@Test
public void givenFruitMap_whenComputeIfAbsentUsed_thenNewEntriesAdded() {
fruitMap.computeIfAbsent("lemon", k -> Double.valueOf(k.length()));
Assertions.assertTrue(fruitMap.containsKey("lemon"));
Assertions.assertEquals(Double.valueOf("lemon".length()), fruitMap.get("lemon"));
}
键 lemon
不存在 → 计算字符串长度作为值 → 新增条目。
4.6. computeIfPresent 方法
仅当键存在时更新值:
@Test
public void givenFruitMap_whenComputeIfPresentUsed_thenValuesUpdated() {
Double oldAppleValue = fruitMap.get("apple");
BiFunction<Double, Integer, Double> powFunction = (x1, x2) -> Math.pow(x1, x2);
fruitMap.computeIfPresent("apple", (k, v) -> powFunction.apply(v, 2));
Assertions.assertEquals(Double.valueOf(Math.pow(oldAppleValue, 2)), fruitMap.get("apple"));
}
apple
存在 → 值更新为平方。
4.7. merge 方法
键存在时用 BiFunction 更新值,不存在时新增条目(使用方法第二个参数作为默认值):
@Test
public void givenFruitMap_whenMergeUsed_thenNewEntriesAdded() {
double defaultValue = 1.25;
BiFunction<Double, Integer, Double> powFunction = (x1, x2) -> Math.pow(x1, x2);
fruitMap.merge("apple", defaultValue, (k, v) -> powFunction.apply(v, 2));
fruitMap.merge("strawberry", defaultValue, (k, v) -> powFunction.apply(v, 2));
Assertions.assertTrue(fruitMap.containsKey("strawberry"));
Assertions.assertEquals(Double.valueOf(defaultValue), fruitMap.get("strawberry"));
Assertions.assertEquals(Double.valueOf(Math.pow(defaultValue, 2)), fruitMap.get("apple"));
}
apple
存在 → 值更新为 defaultValue 的平方strawberry
不存在 → 新增条目,值为 defaultValue
5. 总结
本文介绍了 HashMap 键值更新的多种方法:
- Java 8 前:put 方法、containsKey + put 组合
- **Java 8+**:replace、getOrDefault + put、putIfAbsent、compute、computeIfAbsent、computeIfPresent、merge
实际开发中,根据场景选择最合适的方法:
- 简单更新 →
put
或replace
- 条件更新 →
replace(key, oldVal, newVal)
或computeIfPresent
- 安全新增 →
putIfAbsent
或computeIfAbsent
- 复杂计算更新 →
compute
或merge
完整示例代码见 GitHub 仓库。