区别
HashMap
同时支持 null
键和 null
值,而在 ConcurrentHashMap
中,却都不支持。通过浏览两者关键位置的代码,可以很快得出这个结论。
- [
HashMap
]
对 null
键做特殊处理,并默许 null
值的保存。
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
- [
ConcurrentHashMap
]
明确不允许 null
键和 null
值的存在。
final V putVal(K key, V value, boolean onlyIfAbsent) {
if (key == null || value == null) throw new NullPointerException();
...
}
原因
但是,为何 ConcurrentHashMap
要选择不支持 null
键和 null
值呢?
首先,我们将问题拆分两个小问题,即为何不支持 null
键,以及为何不支持 null
值。
一、为何不支持 null
值?
其实,对于这个问题就有人就问过 ConcurrentHashMap
的作者 Doug Lea
,以下是他的回答的邮件内容。
Tutika Chakravarthy wrote:
> Hi ,
> I would like to replace some Hashmaps in our
> application, which are prone to multi threading issues
> with ConCurrentHashMap.
>
> Currently we keep null key and values in hashmap
> without any issues as HashMap allows them.
>
> But ConcurrentHashMap does not allow any null key and
> values .
>Try to take Holger's advice. As mostly an aside though...
The main reason that nulls aren't allowed in ConcurrentMaps
(ConcurrentHashMaps, ConcurrentSkipListMaps) is that
ambiguities that may be just barely tolerable in non-concurrent
maps can't be accommodated. The main one is that if
map.get(key) returns null, you can't detect whether the
key explicitly maps to null vs the key isn't mapped.
In a non-concurrent map, you can check this via map.contains(key),
but in a concurrent one, the map might have changed between calls.Further digressing: I personally think that allowing
nulls in Maps (also Sets) is an open invitation for programs
to contain errors that remain undetected until
they break at just the wrong time. (Whether to allow nulls even
in non-concurrent Maps/Sets is one of the few design issues surrounding
Collections that Josh Bloch and I have long disagreed about.)>
> It is very difficult to check for null keys and values
> in my entire application .
>Would it be easier to declare somewhere
static final Object NULL = new Object();
and replace all use of nulls in uses of maps with NULL?-Doug
Doug Lea
如此设计最主要的原因是,不容忍在并发场景下出现歧义!!!
比如,通过调用 map.get(key)
获取值,但是返回结果为 null
,如果是在并发场景下,是无法判断是键不存在,还是键对应的值为 null
。在非并发的场景,可以通过 map.contains(key)
的方式检查,但是并发场景中,两次调用之间数据是会发生变化的。
可以参考以下代码,在并发场景下,即使 if
条件满足,键存在,但是并不能保证之后获取值时,键还存在,键是有可能被其它线程删除的。
if (map.contains(key)) {
obj = map.get(key);
}
二、为何不支持 null
键?
从上面的 Doug Lea
的回复可以看出,他本人和 HashMap
的作者 Josh Bloch
在设计上是存在分歧的,他认为在 Maps
(及 Sets
)中允许出现 null
,会导致一些未知的异常在特殊的情况下发生。
个人认为在实现上是可以支持的,但是作者的设计风格是想尽量避免不必要的隐藏异常。但是如果你确实需要在 ConcurrentHashMap
中使用 null
怎么办呢?
三、替代方案
可以使用一个特殊的静态空对象来代替 null
。
public static final Object NULL = new Object();
网友评论