【问题标题】:Map.ofEntries gives Null Pointer Exception on checking NULL key using containsKey()Map.ofEntries 在使用 containsKey() 检查 NULL 键时给出 Null 指针异常
【发布时间】:2021-10-05 09:51:43
【问题描述】:

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

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

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

令人惊讶的是,当 Null 键被传递给 containsKey() 方法时,我得到 Null Pointer Exception

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 实现可以选择是否支持空键和空值。这可能是一个设计缺陷,但它是规范,所以如果你只有Map,你不知道它是否会支持它们。
  • 让代码看起来花哨是进行任何更改的糟糕理由,尤其是对于已经工作的代码。让它更清晰更容易阅读将是另一回事。

标签: java collections java-8 nullpointerexception


【解决方案1】:

代替 HashMap,我开始使用 Map.ofEntries 来构建我的地图

令人惊讶的是,当传递 Null 键时,我得到 Null Pointer Exception 到 containsKey() 方法

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

一些地图实现对其可能包含的键和值有限制。例如,一些实现禁止 null 键和值,有些对它们的类​​型有限制 键。尝试插入不合格的键或值会引发 未经检查的异常,通常是NullPointerExceptionClassCastException尝试查询是否存在 不合格的键或值可能会引发异常,或者它可能只是 返回假;一些实现会表现出前一种行为 有些人会展示后者。

(已添加重点。)

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

另一方面,正如 Andreas 所展示的,HashMap 被记录为允许空键和值,因此当传递空参数时,它的 containsKey() 方法有望正常完成。

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

您有两个主要选择:

  • 如果您想继续(直接)使用通过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() 更简洁一些。当然,如果您不关心地图是否可修改,那么您不必将其放入不可修改的包装器中。

【讨论】:

  • 哈哈!!取一张不可修改的地图,让它可修改,只是为了让它再次不可修改。抱歉,它适用于手头的任务 (+1),它只是触动了我的有趣的骨头。
  • 当然,你可以用Map.of("k1", "v1")代替Map.ofEntries( Map.entry("k1", "v1") ),你可以用Collections.unmodifiableMap( new HashMap&lt;String, String&gt;( Map.of("k1", "v1") ) )代替Collections.singletonMap("k1", "v1")
  • 确实是的,@Holger,正如这个答案已经提到的那样,尽管没有示例代码。我演示了Map.ofEntries(),因为 OP 专门询问了这一点。如果 OP 的真实代码需要设置一个包含十多个条目的映射,那么 Map.of() 在那里不是一个可行的选择。
【解决方案2】:

Map 的 javadoc 说:

不可修改的地图

Map.ofMap.ofEntriesMap.copyOf 静态工厂方法提供了一种方便的方式来创建不可修改的地图。这些方法创建的 Map 实例具有以下特点:

  • 他们是unmodifiable。无法添加、删除或更新键和值。在 Map 上调用任何 mutator 方法总是会导致 UnsupportedOperationException 被抛出。但是,如果包含的键或值本身是可变的,则可能会导致 Map 的行为不一致或其内容似乎发生变化。
  • 他们不允许 null 键和值。尝试使用 null 键或值创建它们会导致 NullPointerException
  • ...

相比之下,HashMap 的 javadoc 说:

Map 接口的基于哈希表的实现。此实现提供了所有可选的映射操作,并允许null 值和null。 (HashMap 类大致相当于Hashtable,除了它是不同步的并且允许空值。)这个类不保证映射的顺序;特别是,它不保证订单会随着时间的推移保持不变。

...

【讨论】:

  • 所讨论的映射不允许空键使得该映射的containsKey() 方法允许在出现空参数时抛出 NPE,但不需要它这样做。因此,情况比这个答案似乎暗示的要微妙一些。
【解决方案3】:

这是不可修改地图的实现细节,由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::containsKeyJavadoc,你可以看到它明确表示该方法可能会或可能不会产生NPE。

如果此映射包含指定键的映射,则返回 true。更多的 正式地,当且仅当此映射包含键 k 的映射时才返回 true,例如 Objects.equals(key, k)。 (最多可以有一个这样的映射。)

参数: key – 待测试在此映射中是否存在的键

返回: 如果此映射包含指定键的映射,则为 true

抛出: ClassCastException – 如果键的类型不适合此映射(可选)

NullPointerException – 如果指定的键为空并且此映射不允许空键(可选)

【讨论】:

  • 不,这不是一个实现细节,它是一个规范,即它记录在 Map 的 javadoc 中。
  • 我会说Maps javadoc 允许一些歧义,并且由每个实现来决定要做什么。所以,我认为这是一个实现细节
  • 我只指Map::containsKey 的javadoc。 Map 的 Javadoc 说 Map.ofMap.ofEntriesMap.copyOf 是返回不可修改的映射,这是返回 NPE 所必需的,这些没有歧义,但在 general @987654343 @'s javadoc 允许歧义。
  • 我确实(仍然)认为将其称为实现细节是一种误导,但经过进一步审查,我发现Map.ofEntries() 提供的Map 不允许空键这一事实并不要求其containsKey() 方法在出现空参数时抛出 NPE。它仍然可以选择返回false。按照 JavaDocs 中的链接了解其中使用的“可选”的含义。
  • @geobreze,我承认可以将其称为实现细节。这并不意味着使用该描述不会产生误导。 “实施细节”(对我来说)暗示了一些完全没有记录的东西,课程的用户不需要知道也不应该关心。在这种情况下,这些都不适用。
猜你喜欢
  • 1970-01-01
  • 2016-03-20
  • 1970-01-01
  • 2015-04-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-01-20
相关资源
最近更新 更多