255. Java 集合 - 使用 `compute()` 系列方法操作 Map 的值

99 阅读4分钟

255. Java 集合 - 使用 compute() 系列方法操作 Map 的值

Map 接口提供了三种有用的 计算/更新值 的方法:compute(), computeIfPresent()computeIfAbsent()。这些方法非常灵活,可以帮助我们在 Map 中添加或更新键值对,而不需要显式地检查键是否存在。

1. 🛠️ compute(), computeIfPresent()computeIfAbsent() 方法概述

这三种方法的参数结构如下:

  • key:操作的目标键
  • value:现有值(对于 compute()computeIfPresent()
  • BiFunctionFunction:执行映射操作的函数(对于 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

互动问题

问题1computeIfAbsent() 为什么比 putIfAbsent() 更高效?

答案示例:computeIfAbsent() 只在键不存在时创建新值,而 putIfAbsent() 每次都创建一个新值,不管键是否已存在。

问题2compute() 方法的返回值和 put() 方法的返回值有什么区别?

答案示例:compute() 返回的是新的值,而 put() 返回的是旧值。

问题3computeIfAbsent() 方法的映射函数是什么时候执行的?

答案示例:computeIfAbsent() 的映射函数只有在键不存在时才会被执行。