255. Java 集合 - 使用 compute() 系列方法操作 Map 的值
Map 接口提供了三种有用的 计算/更新值 的方法:compute(), computeIfPresent() 和 computeIfAbsent()。这些方法非常灵活,可以帮助我们在 Map 中添加或更新键值对,而不需要显式地检查键是否存在。
1. 🛠️ compute(), computeIfPresent() 和 computeIfAbsent() 方法概述
这三种方法的参数结构如下:
key:操作的目标键value:现有值(对于compute()和computeIfPresent())BiFunction或Function:执行映射操作的函数(对于compute()是BiFunction,对于computeIfAbsent()是Function)
🔹 compute() 方法
compute()方法根据现有的键值对(如果有的话)重新计算并替换该键的值。- 参数:
- 第一个参数:键(key)
- 第二个参数:当前键绑定的值(如果键存在,否则为
null) - 返回值:新的值或
null(如果返回值为null,则键将从 Map 中删除)
🔹 computeIfPresent() 方法
computeIfPresent()方法仅在键存在且绑定的值不为null时才会执行映射操作。- 参数:
- 第一个参数:键(key)
- 第二个参数:现有值(如果键存在且值不为
null) - 返回值:新的值或
null(如果返回null,则键将从 Map 中删除)
🔹 computeIfAbsent() 方法
computeIfAbsent()方法用于在 Map 中不存在指定键时计算并添加一个值。- 参数:
- 第一个参数:键(key)
- 第二个参数:用于计算新值的函数,该函数接收键作为参数,并返回计算出的新值
- 返回值:新值或
null(如果返回值为null,则键将不被插入 Map)
🔸 注意:
- 如果映射函数返回
null,则对应的键将被从 Map 中移除,因为不能有值为null的键值对。 compute()和put()方法的语义不同:put()返回的是旧值,而compute()返回的是新值。
2. 🔄 示例:使用 computeIfAbsent() 创建以单词长度为键的 Map
🌱 目标:根据单词列表创建一个 Map,其中键是单词的长度,值是包含该长度单词的列表。
假设我们有一个字符串列表:
List<String> strings = List.of("one", "two", "three", "four", "five", "six", "seven");
我们希望得到一个 Map,如下所示:
3 :: [one, two, six]
4 :: [four, five]
5 :: [three, seven]
📝 传统方式(不使用 computeIfAbsent()):
Map<Integer, List<String>> map = new HashMap<>();
for (String word : strings) {
int length = word.length();
if (!map.containsKey(length)) {
map.put(length, new ArrayList<>());
}
map.get(length).add(word);
}
map.forEach((key, value) -> System.out.println(key + " :: " + value));
🖨️ 输出结果:
3 :: [one, two, six]
4 :: [four, five]
5 :: [three, seven]
这种方式可以工作,但是它在每次循环时都要检查是否存在该键,如果没有就创建一个新的列表。
📝 改进方式(使用 putIfAbsent()):
for (String word : strings) {
int length = word.length();
map.putIfAbsent(length, new ArrayList<>());
map.get(length).add(word);
}
map.forEach((key, value) -> System.out.println(key + " :: " + value));
这种方式更加简洁,但依然存在一个问题:每次都要创建一个新的空列表,虽然 putIfAbsent() 只会在 Map 中没有该键时插入值,但仍然是一个多余的操作。
📝 最优方式(使用 computeIfAbsent()):
for (String word : strings) {
int length = word.length();
map.computeIfAbsent(length, key -> new ArrayList<>())
.add(word);
}
map.forEach((key, value) -> System.out.println(key + " :: " + value));
🖨️ 输出结果:
3 :: [one, two, six]
4 :: [four, five]
5 :: [three, seven]
✅ 使用 computeIfAbsent() 可以在不存在该键时仅创建空列表,这比使用 putIfAbsent() 更高效。computeIfAbsent() 方法只会在 Map 中不存在该键时调用映射函数,并返回新创建的空列表,之后我们可以直接在这个列表上添加单词。
3. 🏁 为什么 computeIfAbsent() 更高效?
- 高效性:
computeIfAbsent()只会在键不存在时执行映射操作,这避免了不必要的列表创建。相比之下,putIfAbsent()每次都会创建一个新列表,即使该键已经存在,虽然它不会覆盖已存在的值。 - 简洁性:
computeIfAbsent()将键的检查和映射操作合并到一个方法中,简化了代码逻辑。
4. 🎯 总结
| 方法 | 功能说明 | 返回值 |
|---|---|---|
compute(key, remappingFunction) | 根据现有值计算并更新 Map 中的键值对 | 新值或 null |
computeIfPresent(key, remappingFunction) | 仅当键存在且值不为 null 时更新值 | 新值或 null |
computeIfAbsent(key, mappingFunction) | 仅当键不存在时创建并插入新值 | 新值或 null |
互动问题
问题1:computeIfAbsent() 为什么比 putIfAbsent() 更高效?
答案示例:
computeIfAbsent()只在键不存在时创建新值,而putIfAbsent()每次都创建一个新值,不管键是否已存在。
问题2:compute() 方法的返回值和 put() 方法的返回值有什么区别?
答案示例:
compute()返回的是新的值,而put()返回的是旧值。
问题3:computeIfAbsent() 方法的映射函数是什么时候执行的?
答案示例:
computeIfAbsent()的映射函数只有在键不存在时才会被执行。