完整教程:computeIfAbsent用法讲解
这是Java 中 Map 接口的 computeIfAbsent 方法。这是一个非常强大且实用的方法,可以极大地简化代码。
方法定义
首先,我们来看一下它的方法签名:
V computeIfAbsent(K key, Function<
? super K, ? extends V> mappingFunction)
- key: 要与指定值关联的键。
- mappingFunction: 一个函数式接口(通常是 Lambda 表达式或方法引用)。它接受 key 作为输入,并计算(Compute)出要返回的值。只有当 key 对应的映射不存在(Absent)或为 null 时,这个函数才会被调用。
- 返回值: 返回与指定键关联的当前(现有的或新计算的)值;如果计算后的值仍为 null,则返回 null。
核心思想
computeIfAbsent 的行为可以概括为一句话::“如果不存在,则计算并放入”
检查 Map 中是否存在这个 key。如果不存在(或者对应的 value 是 null),则使用 mappingFunction 计算出一个新值,将这个 key-value 对放入 Map,并返回这个新值。如果 key 已经存在,则直接返回已存在的 value,mappingFunction 不会被调用。
它的内部逻辑类似于以下代码:
if (map.get(key) == null) {
V newValue = mappingFunction.apply(key);
// 根据key计算新值
if (newValue != null) {
map.put(key, newValue);
// 如果新值不为空,则放入Map
return newValue;
}
}
return map.get(key);
// 如果key已存在或新值为空,返回现有的值(可能为null)
优点
在没有 computeIfAbsent 之前,我们通常这样写代码:
Map<
String, List<
String>
> map = new HashMap<
>();
String key = "fruits";
// 传统写法
List<
String> list = map.get(key);
if (list == null) {
list = new ArrayList<
>();
list.add("Apple");
map.put(key, list);
} else {
list.add("Apple");
}
// 使用 computeIfAbsent
List<
String> list = map.computeIfAbsent(key, k ->
new ArrayList<
>());
list.add("Apple");
优点对比:
简洁性: 将检查、计算、放入和返回多个步骤合并成了一个原子操作,代码更加简洁。
原子性: 在多线程环境下,如果使用的是 ConcurrentHashMap,computeIfAbsent 的执行是线程安全的。整个“检查-计算-放入”过程是一个同步块,避免了竞态条件。
表达清晰: 代码的意图非常明确——“如果这个 key 没有值,就创建一个新的列表给我”。
使用场景
场景 1:分组归类(最常见)
这是一个非常经典的用法,用于构建一个“每个键对应一个集合”的 Map。
Map<
String, List<
Student>
> studentsByClass = new HashMap<
>();
for (Student student : allStudents) {
// 如果 className 不存在,就创建一个新 ArrayList 并放入Map
// 然后返回这个 List(无论是新创建的还是已存在的),接着添加 student
studentsByClass.computeIfAbsent(student.getClassName(), k ->
new ArrayList<
>())
.add(student);
}
场景 2:缓存(Lazy Loading)
实现一个简单的缓存,只有在需要时才计算值。
Map<
String, BigDecimal> priceCache = new HashMap<
>();
public BigDecimal getPrice(String productId) {
return priceCache.computeIfAbsent(productId, this::calculatePriceFromDatabase);
}
// 这是一个耗时的方法,只在第一次获取某个 productId 的价格时调用
private BigDecimal calculatePriceFromDatabase(String productId) {
// ... 模拟复杂的数据库查询或网络请求
return BigDecimal.TEN;
}
场景 3:初始化复杂对象
确保 Map 中的每个键都有一个初始化好的、非空的复杂对象。
Map<
Integer, Config> configMap = new HashMap<
>();
// 获取 ID 为 123 的配置,如果没有则用默认配置初始化
Config config = configMap.computeIfAbsent(123, id ->
new Config(...));
config.doSomething();
注意事项
mappingFunction 不应返回 null: 如果 mapping function 计算后返回 null,则不会创建任何映射,该方法也会返回 null。这通常不是你想要的,可能会导致后续的 NullPointerException。
ConcurrentHashMap 的线程安全性: 在 ConcurrentHashMap 中,computeIfAbsent 是线程安全的。整个计算过程是在锁内执行的,这意味着 mappingFunction 不应该是一个耗时很长的任务,否则会成为性能瓶颈。
递归调用: 严禁在 ConcurrentHashMap 的 computeIfAbsent 的 mappingFunction 中尝试再次修改这个 Map 本身(例如,再次调用 computeIfAbsent 或 put),这可能会导致死锁。

浙公网安备 33010602011771号