ConcurrentHashMap 禁止 null 键和 null 值,是为了确保 get(key) 返回 null 时语义唯一——仅表示 key 不存在;若允许 null 值,则无法区分“key 不存在”和“key 存在但 value 为 null”,并发下无法通过 containsKey() 可靠辅助判断,故源码在 putVal() 开头强制校验并抛 NPE。

ConcurrentHashMap 禁止 null 键和 null 值,根本原因不是“写死的限制”,而是为了在多线程环境下消除返回值的二义性——让 get(key) 返回 null 时,含义唯一且可信赖。
ConcurrentHashMap 的 get(null) 必须有确定语义
在并发场景中,get(key) 返回 null 只能代表一种情况:key 不存在。如果允许 null 值,就会出现两种可能:
- key 根本不在 map 中 → 返回
null - key 存在,但对应 value 就是
null→ 也返回null
这种二义性在单线程的 HashMap 中可通过 containsKey() 辅助判断;但在并发下,containsKey() 和 get() 之间可能被其他线程插入、删除或修改,无法保证原子性,结果不可靠。
源码层面直接拦截,杜绝歧义入口
ConcurrentHashMap 在 putVal() 开头就强制校验:
立即学习“Java免费学习笔记(深入)”;
if (key == null || value == null) throw new NullPointerException();- 校验发生在任何实际操作之前,不依赖哈希计算(避免
null.hashCode()报错) - 所有 public 修改方法(
put、putIfAbsent、compute等)最终都走这个逻辑
对比 HashMap:单线程下“可回头确认”
HashMap 允许 null 键(仅一个,存于桶索引 0)和任意数量的 null 值,因为它不要求 get() 的返回值自解释:
-
map.get("k")返回null→ 再调一次map.containsKey("k")就能明确区分 - 单线程执行流稳定,两次调用间 map 不会突变
而 ConcurrentHashMap 的设计哲学是:每个读操作(尤其是 get)本身就要具备确定语义,不依赖额外检查。
替代方案:用占位对象代替 null
如果业务逻辑需要表达“缺失值”或“未初始化”,推荐显式定义语义清晰的哨兵对象:
- 静态常量:
private static final Object MISSING = new Object(); - 空集合:
Collections.emptySet()或Optional.empty()(注意包装成本) - 字符串占位符:
"NULL_VALUE"(仅限 String 场景,需约定)
这样既避开 null 陷阱,又保留了业务可读性与线程安全性。


















