HashMap允许一个null键,因其hash()方法将null键哈希值固定为0并绕过hashCode()调用;ConcurrentHashMap和Hashtable因并发安全与语义歧义禁止null键;LinkedHashMap和WeakHashMap支持null键,TreeMap和EnumMap不支持。

hash() 方法对 null 的特殊处理是技术支点
HashMap 允许一个 null 键,根本原因不在“设计宽松”,而在于它自己接管了哈希计算逻辑——hash() 方法里那行三目运算:(key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16)。当 key 为 null 时,直接返回 0,跳过 key.hashCode() 调用,彻底避开空指针异常。
这个固定哈希值 0 决定了所有 null 键都落在 table[0] 位置;后续查找、插入、删除时,只用 == null 判断键是否匹配(不用 equals()),天然保证“最多一个 null 键”。
为什么 ConcurrentHashMap 和 Hashtable 就不行?
不是它们“做不到”,而是并发语义下 null 会引发歧义:
-
ConcurrentHashMap.put()开头就显式检查:if (key == null || value == null) throw new NullPointerException(); -
Hashtable同样在put()中调用key.hashCode()前不做判空,null直接触发 NPE - 核心问题在于:多线程中
get(key) == null无法区分“键不存在”还是“键存在但值为null”,而containsKey()又有竞态窗口,没法安全补位
实际写代码时怎么安全用 null 键?
允许 ≠ 推荐。真实项目里用 null 当键,90% 的坑都出在读取逻辑上:
立即学习“Java免费学习笔记(深入)”;
- 永远别靠
map.get(null) == null判断null键是否存在——它可能是没这个键,也可能是键存在但值是null - 正确姿势:
map.containsKey(null)先确认键存在,再map.get(null)取值 - 如果业务需要“无意义键”标识(比如默认配置、兜底路由),建议用私有哨兵对象:
private static final Object NO_KEY = new Object();,彻底规避二义性 - 注意序列化:Jackson 默认忽略
null键,Gson 可能报错,跨服务传输前务必实测
扩展:哪些 Map 实现能用 null 键?
只有明确继承并复用 HashMap 哈希逻辑的实现才支持:
-
LinkedHashMap:继承 HashMap,hash()行为一致,支持null键 -
WeakHashMap:底层也走同一套hash(),允许null键(但注意:null键会被立即 GC 清理) -
TreeMap不行——它依赖Comparable或Comparator,null在比较时直接抛NullPointerException -
EnumMap更不行——键类型限定为Enum,编译期就排除null
真正容易被忽略的点是:null 键的“唯一性”不靠 equals() 保障,而靠 JVM 对 == null 的判定,这和所有其他键的行为都不一致——一旦混用自定义键和 null 键,调试时很容易误以为是哈希冲突或重写问题。










