在Java中,向HashMap添加一个null键或值的作用是什么?

91

HashMap允许一个null键和任意数量的null值。它有什么用处?


11
也许问题不在于没有什么困扰我们,而在于我们在困扰它。 - bmargulies
3
在Guava(谷歌集合)中,很多类不允许使用null,原因是95%的情况下不需要null,并且使用null可能会导致bug,这些bug可能难以发现。 - stivlo
奇怪的是,ConcurrentHashMap 不支持空键,而 HashMap 支持。 - codepleb
2
只有 HashMap 允许 null :) - subhashis
7个回答

126

我不确定你的问题是什么,但如果你正在寻求使用 null 键的示例,我经常在映射中使用它们来表示默认情况(即如果给定键不存在应该使用的值):

Map<A, B> foo;
A search;
B val = foo.containsKey(search) ? foo.get(search) : foo.get(null);

HashMap 会特殊处理 null 键(因为不能在 null 对象上调用 .hashCode()),但是 null 值并没有任何特别之处,它们像其他任何值一样存储在 Map 中。


4
如果空对象无法使用 .hashCode(),那么谁决定了 null 键将进入哪个存储位置? - Pacerier
26
HashMap中有一个特殊的方法(putForNullKey)来处理这个问题,它将其存储在表0中。 - Michael Mrozek
1
@MichaelMrozek,你的最后一行代码 B val = foo.containsKey(search) ? foo.get(search) : foo.get(null); 我认为我们可以直接在搜索键上调用get方法,这将得到相同的结果。 B val = foo.get(search); 如果我有什么错误,请纠正我。 - dheerajraaj
6
如果键不存在,你的代码将使val为null;我的代码将其设置为映射中null对应的值。这是重点,我在映射中存储了一个默认非null值在null键处,并在实际键不存在时使用它。 - Michael Mrozek

28

一个例子是用于建模树结构。如果您使用HashMap来表示树结构,其中键是父节点,值是子节点列表,则null键的值将成为根节点。


6
一种使用空值(null)的例子是在将 HashMap 用作昂贵操作(如调用外部 Web 服务)结果的缓存时,该操作可能会返回 null
在地图中放置一个空值(null),然后可以区分给定键的操作未执行的情况(cache.containsKey(someKey) 返回 false)和已执行但返回 null 值的情况(cache.containsKey(someKey) 返回 truecache.get(someKey) 返回 null)。
如果没有空值(null),则必须将某些特殊值放入缓存以指示空响应,或者根本不缓存该响应并每次执行操作。

3
到目前为止,先前的回答只考虑了使用null键的价值,但问题也涉及到任意数量的null值
在HashMap中存储null值的好处与数据库等相同——可以记录一个区别,即有一个空值(例如字符串""),和根本没有值(null)。

2
这是一个关于使用null键的例子,虽有些牵强,但仍然有用处:
public class Timer {
    private static final Logger LOG = Logger.getLogger(Timer.class);
    private static final Map<String, Long> START_TIMES = new HashMap<String, Long>();

    public static synchronized void start() {
        long now = System.currentTimeMillis();
        if (START_TIMES.containsKey(null)) {
            LOG.warn("Anonymous timer was started twice without being stopped; previous timer has run for " + (now - START_TIMES.get(null).longValue()) +"ms"); 
        }
        START_TIMES.put(null, now);
    }

    public static synchronized long stop() {
        if (! START_TIMES.containsKey(null)) {
            return 0;
        }

        return printTimer("Anonymous", START_TIMES.remove(null), System.currentTimeMillis());
    }

    public static synchronized void start(String name) {
        long now = System.currentTimeMillis();
        if (START_TIMES.containsKey(name)) {
            LOG.warn(name + " timer was started twice without being stopped; previous timer has run for " + (now - START_TIMES.get(name).longValue()) +"ms"); 
        }
        START_TIMES.put(name, now);
    }

    public static synchronized long stop(String name) {
        if (! START_TIMES.containsKey(name)) {
            return 0;
        }

        return printTimer(name, START_TIMES.remove(name), System.currentTimeMillis());
    }

    private static long printTimer(String name, long start, long end) {
        LOG.info(name + " timer ran for " + (end - start) + "ms");
        return end - start;
    }
}

如果您试图停止一个不存在的计时器或者已经被停止的计时器,那应该是一个错误而不是被忽略。 - anon
@QPaysTaxes - 取决于您的意图。如果您想要一个轻量级实用程序,可以轻松使用,通常不希望在其中“throw Exception”。此外,试图停止不存在或已停止的计时器并不是调用者通常可以恢复的事情。 - aroth

1
另一个例子:我使用它按日期对数据进行分组。但是有些数据没有日期。我可以使用标题“无日期”将其分组。

0

当地图存储 UI 选择数据时,空键也可以帮助表示 bean 字段。

相应的空字段值在 UI 选择中可以表示为“(请选择)”。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接