为什么HashMap允许key和value为null,但是Concurrent HashMap不允许
首先,讨论value为什么不能为null
如果一个Map允许value为null,那么当调用map.get(key)返回null时,会出现二义性问题。可能会是:
- 这个key不存在于map中;
- 这个key存在于map中,并且其关联的val是null;
单线程环境下可以消除二义性。在单线程环境下使用HashMap,可以通过“先判断再取值”的组合操作来避免这种二义性问题,代码实现如下所示:
if (map.contains(key)) { // 该key存在于map中
Object val = map.get(key); // 从map中取出key对应的val,如果val为null,说明“这个key是存在于map中,并且其关联的val是null”
} else {
// 该key不存在于map中
}
多线程环境下无法消除二义性。在多线程环境下,“先判断再取值”的组合操作不具备原子性。在线程进行判断操作和取值操作之间,可能会有其它线程修改了这个map。具体来说,可能有以下情况:
1. 线程A调用了map.contains(key),返回true;
2. 线程B删除了这个key对应的键值对;
3. 线程A调用map.get(key),返回了null;
所以,线程A通过map.get(key)拿到返回值null,却依然无法区“这个key不存在”还是“这个key存在且其关联的val是null”
接下来,讨论key为什么不能null
HashMap的特殊处理。在HashMap中允许key为空,是因为HashMap对hash值的计算方式做了特殊处理,如下代码所示,key为null的键值在计算得到的哈希值是0,这个键值对会被固定放在数组的0号桶中。所以在HashMap中,key为null的键值对最多只有一个。
static final int hash(Object key) {
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
设计理念。在ConcurrentHashMap中不允许key为空,是因为ConcurrentHashMap的作者认为,允许将null作为key存入容器,容易隐藏业务逻辑中的bug(比如,会将原本应该有值但现在为null的key存入容器,却没报错)
规避特例。此外,如果允许key为空,会在代码中为null编写大量特殊分支(因为,null.hashCode()等方法会抛出空指针异常)。这会使代码变得冗余,而且会拖累并发容器的性能。
#HashMap##Java#记录一些HashMap相关的面试题
