Map中 putIfAbsent、compute、computeIfAbsent、computeIfPresent 学习笔记

本文深入探讨了Java中Map接口的几种实用方法:putIfAbsent、compute、computeIfAbsent及computeIfPresent。通过具体实例展示了这些方法如何高效地更新或获取Map中的元素。

Map中 putIfAbsent、compute、computeIfAbsent、computeIfPresent 学习笔记

putIfAbsent
public class Main {
    public static void main(String[] args) {
        HashMap<String, String> map = new HashMap<>();
        map.put("KeyOne", "ValueOne");
        map.put("KeyTwo", null);
        map.put("KeyThree", "ValueThree");

        // Test putIfAbsent
        // put 方法是当 Key 存在时,替换旧的 Value,返回旧的 Value;
        // putIfAbsent 方法是当 Key 存在时,不进行替换除非 Value 为 null,返回旧的 Value;
        // 当 Key 不存在时, 与 put 方法无区别,将键值对存入 Map 中,返回 null
        String s1 = map.putIfAbsent("KeyOne", "NewValueOne");
        String s2 = map.putIfAbsent("KeyTwo", "NewValueTwo");
        String s3 = map.putIfAbsent("KeyFour", "ValueFour");
        System.out.println("当 Key 存在,且 Value 不为 null:" + s1);
        System.out.println("当 Key 存在,且 Value 为 null:" + s2);
        System.out.println("当 Key 不存在:" + s3);
        System.out.println(map);
    }
}

putIfAbsent:当 Key 存在且 Value 为 null 时,Value会被改变
在这里插入图片描述

	// HashMap 中重写 Map 接口中的default方法 putIfAbsent
	// putVal(hash(key), key, value, true, true) 中倒数第二个参数为 onlyIfAbsent
	@Override
    public V putIfAbsent(K key, V value) {
        return putVal(hash(key), key, value, true, true);
    }
	// put 方法中 onlyIfAbsent 参数为 false
	public V put(K key, V value) {
        return putVal(hash(key), key, value, false, true);
    }

putVal 方法源码:在 onlyIfAbsent 为 false 或者 oldValue 为 null 时替换旧的 Value
在这里插入图片描述

compute
public class Main {
    public static void main(String[] args) {
        HashMap<String, Integer> map = new HashMap<>();

        map.put("KeyOne", 1);
        
		// 不管 Key 存在与否,操作完成后存入 map 中
        Integer exist = map.compute("KeyOne", (k, v) -> v + 1);
        Integer notExist = map.compute("KeyTwo", (k, v) -> {
            if(v == null) return 0;
            return v + 1;
        });

        System.out.println(exist);
        System.out.println(notExist);
        System.out.println(map);
    }
}

在这里插入图片描述

computeIfAbsent
public class Main {
    public static void main(String[] args) {
        HashMap<String, List<String>> map = new HashMap<>();
		
		map.put("KeyOne", null);
		
        // Test computeIfAbsent
        // 如果 Key 没有映射到一个值或者映射到 null,调用 mappingFunction ,
        // 并在 mappingFunction 执行结果不为 null 时,与 Key 关联放入 map
        map.computeIfAbsent("KeyOne", k -> new ArrayList<>());
		
        // 以前得这么写 👇
        // 注意:不是if(!map.containsKey("KeyOne"))
        // if(map.get("KeyOne") == null){
        //     map.put("KeyOne", new ArrayList<>());
        // }
        
        System.out.println(map);
    }
}
computeIfPresent
public class Main {
    public static void main(String[] args) {
        HashMap<String, Integer> map = new HashMap<>();

        map.put("KeyOne", 1);
        
		// 对 map 中已经存在的 Key 的 Value 操作
        map.computeIfPresent("KeyOne", (k, v) -> v + 1);

        System.out.println(map);
    }
}
<think>嗯,用户的问题是关于Java Map接口中putIfAbsentcomputeIfAbsent方法的区别、用法和应用场景。用户提供的站内引用已经包含了比较详细的对比信息,需要整理成更系统的解释。 用户可能是一位Java开发者,正在处理Map相关的逻辑优化。从提问方式看,ta应该有一定编程基础,但对这两个方法的差异还不够清晰。深层需求可能是想避免踩坑(比如空指针或性能浪费),或者想写出更优雅的代码。 需要重点突出三点差异: 1 行为差异:putIfAbsent是直接放值,computeIfAbsent是动态计算值 2 空值处理:前者允许存null,后者在计算函数中返回null会抛异常 3 性能影响:当value计算成本高时,后者能避免无效计算 引用[1]提到"昂贵value"的场景很关键,这是实际开发中容易忽略的性能点。引用[2]的返回值对比也要强调,因为涉及空指针安全。引用[3]补充了put方法的覆盖行为,能帮助理解设计初衷。 考虑用表格呈现核心差异会更直观,但用户要求用中文回答且未明确允许表格。准备改用文字分点描述,但在关键处用数学符号突出差异点(如$computeIfAbsent \rightarrow 延迟计算$)。 应用场景需要补充具体案例,比如缓存构建用computeIfAbsent更自然,而配置项初始化用putIfAbsent更直接。用户可能还关心线程安全,但引用未提及,暂时跳过。 </think>`putIfAbsent` 和 `computeIfAbsent` 是 Java `Map` 接口中用于条件性插入键值对的方法,二者核心区别在于 **值(value)的提供方式** 及其衍生的行为和性能差异。以下是详细对比分析: --- ### **一、核心区别对比** | **特性** | **`putIfAbsent(K key, V value)`** | **`computeIfAbsent(K key, Function<? super K, ? extends V> mappingFunction)`** | |------------------------|------------------------------------------------|--------------------------------------------------------------------------------| | **值提供方式** | 直接传入固定的 `value` 对象 | 传入一个函数(`Function`),按需动态计算 `value` | | **Key 存在时的行为** | 直接返回现有的 `value`(不执行任何操作) | 直接返回现有的 `value`(**不执行函数计算**) | | **Key 不存在时的行为** | 插入传入的 `value`,返回 `null` | 执行函数计算 `value`,插入结果,**返回计算值** | | **空值(null)处理** | 允许插入 `null` 值 | **禁止函数返回 `null`**(抛出 `NullPointerException`) | | **返回值** | 旧值(若存在)或 `null`(若不存在或旧值为`null`) | 旧值(若存在)或 **计算后的新值**(若不存在) | --- ### **二、关键差异详解** 1. **性能优化(昂贵值的计算)** - **`putIfAbsent`**:无论 key 是否存在,调用前需**预先计算好 `value`**。若 key 已存在,则计算是徒劳的。 ```java // 预先计算昂贵的 value(可能浪费) Value expensiveValue = computeExpensively(); map.putIfAbsent(key, expensiveValue); ``` - **`computeIfAbsent`**:仅当 key 不存在时**才触发函数计算**,避免不必要的开销。 ```java // 仅 key 不存在时计算 map.computeIfAbsent(key, k -> computeExpensively()); ``` 2. **空指针安全性** - **`putIfAbsent`**:若 key 不存在,返回 `null`。直接使用返回值需判空: ```java Value v = map.putIfAbsent(key, value); if (v == null) { /* 首次插入 */ } // 需手动判空 ``` - **`computeIfAbsent`**:直接返回有效值(旧值或计算的新值),**无需判空**: ```java Value v = map.computeIfAbsent(key, k -> new Value()); // v 一定非 null(除非函数异常) ``` 3. **对 `null` 值的策略** - **`putIfAbsent`**:允许插入 `null`。后续 `map.get(key)` 返回 `null`,但 `map.containsKey(key)` 返回 `true`。 - **`computeIfAbsent`**:函数**禁止返回 `null`**(抛出异常)。若需存储 `null`,需显式设计(如返回 `Optional.empty()`)。 --- ### **三、典型应用场景** #### **✅ `putIfAbsent` 适用场景** - 值对象已预先存在或计算成本极低时。 - 需要显式支持存储 `null` 值的场景。 - 简单覆盖逻辑(如初始化默认值): ```java // 初始化计数器(若不存在) map.putIfAbsent("counter", 0); ``` #### **✅ `computeIfAbsent` 适用场景** 1. **延迟初始化/缓存** 避免重复创建昂贵对象(如数据库连接、解析结果): ```java Map<String, Connection> connectionCache = new ConcurrentHashMap<>(); Connection conn = connectionCache.computeIfAbsent(dbName, k -> openConnection(k)); ``` 2. **复合值结构构建** 简化嵌套集合的创建(如 `Map<K, List<V>>`): ```java Map<String, List<String>> groups = new HashMap<>(); // 自动为不存在的 key 创建新 List groups.computeIfAbsent("groupA", k -> new ArrayList<>()).add("Item1"); ``` 3. **原子性构建复杂对象** 函数内可包含多步逻辑: ```java map.computeIfAbsent(userId, id -> { Profile profile = fetchProfileFromDB(id); return enrichProfile(profile); // 复杂处理 }); ``` --- ### **四、示例代码对比** ```java Map<String, AtomicInteger> counterMap = new HashMap<>(); // 使用 putIfAbsent(需额外检查) AtomicInteger counter = counterMap.get("count"); if (counter == null) { counter = new AtomicInteger(0); AtomicInteger old = counterMap.putIfAbsent("count", counter); if (old != null) counter = old; // 处理并发冲突 } counter.incrementAndGet(); // 使用 computeIfAbsent(一行解决) counterMap.computeIfAbsent("count", k -> new AtomicInteger(0)).incrementAndGet(); ``` --- ### **总结** | **方法** | **核心优势** | **注意事项** | |-------------------|---------------------------------------|----------------------------------| | `putIfAbsent` | 简单直观,支持 `null` | 可能浪费计算,返回值需判空 | | `computeIfAbsent` | 避免无效计算,空指针安全,简化嵌套结构 | 禁止函数返回 `null`,需确保幂等性 | > **实践建议**:优先使用 `computeIfAbsent`,除非需显式存储 `null` 或 `value` 已预先计算且成本极低[^1][^2][^3]。 --- ### 相关问题 1. 为什么 `computeIfAbsent` 的函数不允许返回 `null`?这种设计有何利弊? 2. 在多线程环境下使用 `computeIfAbsent` 需要注意哪些问题? 3. 如何用 `computeIfAbsent` 实现一个线程安全的懒加载缓存? 4. `ConcurrentHashMap` 的 `computeIfAbsent` 方法是否存在死锁风险?如何避免?
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值