Map.ofEntries 使用 containsKey() 检查 NULL 键时给出空指针异常

Map.ofEntries gives Null Pointer Exception on checking NULL key using containsKey()

我之前使用 HashMap 从常量映射中获取键。

containsKey() 传递 NULL 键 时,我曾经得到 FALSE

为了让代码看起来更漂亮,我尝试了 java-8。因此,我开始使用 Map.ofEntries 来构建我的地图,而不是 HashMap

令人惊讶的是,当向 containsKey() 方法

传递空键时,我得到了 空指针异常
String str = null;

Map<String,String> hashMap = new HashMap<>();
hashMap.put("k1", "v1");
System.out.print(hashMap.containsKey(str)); // This gives false

Map<String,String> ofEntriesMap = Map.ofEntries( Map.entry("k1", "v1")); 
System.out.print(ofEntriesMap.containsKey(str)); // Why this gives Null Pointer Exception ?

我无法弄清楚,为什么它在 Map.ofEntries 处表现不同。

处理这种情况的最佳方法是什么?

这是不可修改地图的实现细节,由 Map.ofEntries 创建。

当您将 null 键添加到 HashMap 时,它会计算 null 的哈希值等于 0

static final int hash(Object key) {
    int h;
    return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}

但是 Map.ofEntries 如果只提供一对则创建 ImmutableCollections.Map1 否则 ImmutableCollections.MapN

这里是 ImmutableCollections.Map1::containsKey

的实现
public boolean containsKey(Object o) {
    return o.equals(k0); // implicit nullcheck of o
}

您可以看到评论说 NullPointerException 是预期的行为。至于 ImmutableCollections.MapN::containsKey 它使用显式空检查。

public boolean containsKey(Object o) {
        Objects.requireNonNull(o);
        return size > 0 && probe(o) >= 0;
}

如果你参考Map::containsKey Javadoc,你可以看到它明确表示此方法可能会或可能不会产生NPE。

Returns true if this map contains a mapping for the specified key. More formally, returns true if and only if this map contains a mapping for a key k such that Objects.equals(key, k). (There can be at most one such mapping.)

Params: key – key whose presence in this map is to be tested

Returns: true if this map contains a mapping for the specified key

Throws: ClassCastException – if the key is of an inappropriate type for this map (optional)

NullPointerException – if the specified key is null and this map does not permit null keys (optional)

Map 的 javadoc 说:

Unmodifiable Maps

The Map.of, Map.ofEntries, and Map.copyOf static factory methods provide a convenient way to create unmodifiable maps. The Map instances created by these methods have the following characteristics:

  • They are unmodifiable. Keys and values cannot be added, removed, or updated. Calling any mutator method on the Map will always cause UnsupportedOperationException to be thrown. However, if the contained keys or values are themselves mutable, this may cause the Map to behave inconsistently or its contents to appear to change.
  • They disallow null keys and values. Attempts to create them with null keys or values result in NullPointerException.
  • ...

相比之下,HashMap 的 javadoc 说:

Hash table based implementation of the Map interface. This implementation provides all of the optional map operations, and permits null values and the null key. (The HashMap class is roughly equivalent to Hashtable, except that it is unsynchronized and permits nulls.) This class makes no guarantees as to the order of the map; in particular, it does not guarantee that the order will remain constant over time.

...

instead of HashMap, I started using Map.ofEntries to build my map

Surprisingly, I got Null Pointer Exception when a Null key was passed to containsKey() method

java.util.Map 的文档部分说:

Some map implementations have restrictions on the keys and values they may contain. For example, some implementations prohibit null keys and values, and some have restrictions on the types of their keys. Attempting to insert an ineligible key or value throws an unchecked exception, typically NullPointerException or ClassCastException. Attempting to query the presence of an ineligible key or value may throw an exception, or it may simply return false; some implementations will exhibit the former behavior and some will exhibit the latter.

(强调已添加。)

正如已经观察到的,通过Map.ofEntries()创建的地图就是这样的实现。具体来说,它们不允许空键和空值。尽管没有记录它们的 containsKey() 方法是否会在出现空参数时执行抛出选项,但您在使用它们时需要考虑到这种可能性。

另一方面,正如 Andreas 还展示的那样,HashMap 被记录为允许空键和空值,因此它的 containsKey() 方法在传递空参数时预计会正常完成。

What is the best way to handle this situation ?

您有两个主要选择:

  • 如果您想继续(直接)使用通过 Map.ofEntries() 创建的地图,那么您必须避免测试它是否包含空键。因为您知道它不能包含空键,所以不需要这样的测试。

  • 如果您希望能够测试地图中是否存在空键,尤其是如果您希望选择其中包含空键或空值,则不得使用Map.ofEntries() 创建它。但是,您可以使用 Map.ofEntries() 初始化 它。例如:

    Map<String, String> myMap = Collections.unmodifiableMap(
        new HashMap<String, String>(
            Map.ofEntries(
                Map.Entry("k1", "v1")
            )
        )
    );
    

    另请注意,如果您在地图中放置的条目少于 11 个,则 Map.of()Map.ofEntries() 更整洁一些。而且,当然,如果您不关心地图是否可修改,那么您不必将其放入不可修改的包装器中。