HashMap 通过 hash() 方法对 null 键返回固定哈希值 0 实现安全支持,且用 == 判断 null 键唯一性;但 get(null) 返回 null 存在二义性,须配合 containsKey(null) 判断存在性,ConcurrentHashMap 和 Hashtable 因并发语义模糊而禁止 null 键。

hash() 方法怎么让 null 键不崩?
核心就一行:static final int hash(Object key) { return (key == null) ? 0 : (key.hashCode() ^ (key.hashCode() >>> 16)); }。当 key == null,它跳过 key.hashCode() 调用,直接返回 0 —— 这是整个机制的支点。没有这步兜底,key.hashCode() 在 null 上必然抛 NullPointerException。
为什么只能有一个 null 键?
因为哈希值固定为 0,所有 null 键都会被定位到桶数组的 table[0] 位置;后续插入时,HashMap 用 == null(不是 equals())判断是否为同一个键,天然保证“同键覆盖”。所以你反复 put(null, "a")、put(null, "b"),最后只留一个。
get(null) 返回 null,到底有没有这个键?
这是最常踩的坑:map.get(null) 返回 null,可能是键不存在,也可能是键存在但值就是 null。不能靠这个做存在性判断。
- ✅ 正确做法:先调
map.containsKey(null)确认键存在,再调map.get(null)取值 - ⚠️ 避免写法:
if (map.get(null) != null)或map.get(null) == null来推断键是否存在 - ? 更健壮的选择:用哨兵对象代替
null键,比如private static final Object NO_KEY = new Object();
为什么 ConcurrentHashMap 和 Hashtable 就不行?
不是技术做不到,而是设计取舍不同:
立即学习“Java免费学习笔记(深入)”;
-
Hashtable在put()开头就if (value == null) throw new NullPointerException(),且直接调key.hashCode()—— null 必崩 -
ConcurrentHashMap显式禁止null键/值,根本原因是并发语义模糊:多线程下get(key) == null无法区分“键不存在”还是“键存在但值为 null”,而containsKey()和get()之间存在竞态窗口 - HashMap 是单线程友好型设计,宁可接受二义性,也要换灵活性;后两者是线程安全优先,宁可砍掉功能,也要堵住歧义漏洞
真正要注意的,不是“能不能用 null 键”,而是“用了之后,所有读取逻辑是否都规避了二义性判断”。生产代码里,哪怕只在测试或配置场景用一次 null 键,只要没统一处理好 containsKey() + get() 的配合,就容易埋下静默 bug。










