HashMap允许null键是因为其hash()方法对null返回0,使null键固定存入table[0],并通过==判断而非equals()保证唯一性;而HashTable和ConcurrentHashMap因线程安全设计显式禁止null键。

HashMap的null键是怎么存进去的?
因为 hash() 方法对 null 做了特殊处理:当 key == null 时,直接返回 0,不调用 key.hashCode() —— 避开了空指针异常。这个哈希值 0 会定位到桶数组(Node[] table)的索引 0 位置。
- 所有
null键最终都落在数组下标0处,后续插入时通过== null判断是否为同一个 key(不是用equals()) - 由于只走一次
== null比较,且哈希值固定,所以天然保证“最多一个null键” - 你调用
map.get(null)时,内部也是先算hash(null) → 0,再遍历table[0]链表或红黑树,找那个key == null的节点
为什么HashTable和ConcurrentHashMap就不行?
它们在 put() 入口就做显式判空,且没绕过 hashCode() 调用:
-
HashTable源码里有if (value == null) throw new NullPointerException(),且int hash = key.hashCode()直接执行 ——key为null必崩 -
ConcurrentHashMap同样禁止null键/值,核心原因是并发歧义:比如get(key) == null时,无法区分“key 不存在”还是“key 存在但 value 是null”,而containsKey()又不能完全替代(存在竞态窗口) - 设计哲学不同:HashMap 是单线程友好、灵活优先;HashTable 和 ConcurrentHashMap 是线程安全优先,宁可牺牲灵活性来堵住语义漏洞
实际写代码时怎么安全用 null 键?
允许 ≠ 推荐。null 键容易引发逻辑混淆,尤其在读取时:
- 永远别靠
map.get(null) == null判断 null 键是否存在 —— 它可能是没这个键,也可能是键存在但值是null - 正确做法是:先用
map.containsKey(null)确认键存在,再用map.get(null)取值 - 如果业务中频繁需要“无 key 标识”,建议改用哨兵对象(如
private static final Object NO_KEY = new Object();),避免 null 带来的二义性 - 注意序列化:某些 JSON 库(如 Jackson)默认把
null键忽略或报错,生产环境若涉及跨语言传输,要提前验证
源码级验证:看一眼 JDK 17 的 hash() 实现
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
就这一行三目运算,是整个 null 键可行性的技术支点。它不依赖 Object.hashCode() 的契约,而是由 HashMap 自己接管语义 —— 这也是为什么只有 HashMap(及 LinkedHashMap、WeakHashMap 等继承者)能这么干,而其他 Map 实现必须各自实现自己的 null 处理逻辑或直接拒绝。
立即学习“Java免费学习笔记(深入)”;
真正容易被忽略的,不是“能不能放”,而是“取出来之后怎么解释”。null 键本身很轻量,但它的语义重量,往往在三个月后的某次线上排查里才突然显现。










