为什么Map.of不允许空键和值?


76

在Java 9,新的工厂方法已经出台了ListSetMap接口。这些方法允许快速实例化一行中包含值的Map对象。现在,如果我们考虑:

Map<Integer, String> map1 = new HashMap<Integer, String>(Map.of(1, "value1", 2, "value2", 3, "value3"));
map1.put(4, null);

如果我们这样做,则上面的内容毫无例外地被允许:

Map<Integer, String> map2 = Map.of(1, "value1", 2, "value2", 3, "value3", 4, null );

它抛出:

Exception in thread "main" java.lang.NullPointerException
    at java.base/java.util.Objects.requireNonNull(Objects.java:221)
..

我无法获得,为什么在第二种情况下不允许null

我知道HashMap可以将null作为键和值,但是在Map.of的情况下为什么要限制它呢?

java.util.Set.of("v1", "v2", null)和的情况相同java.util.List.of("v1", "v2", null)


21
这个问题是错的。应该是:为什么HashMap允许空值?
ZhekaKozlov

2
@ZhekaKozlov当我们能够使用HashMap包装地图时,它应该允许hashmap的功能,或者不应允许Hashmap对其进行包装。由于fn的“ of”与不变性有关,因此应该在ConcurrentMap中存在,可能会被ConcurrentHashmap扩展。
hi.nitish

@ hi.nitish:也许是时候接受答案了。😉
尼古拉PARLOG

禁止我可以理解的空键,但空值对我来说似乎是可行的
mjj1409

Answers:


77

正如其他人所指出的,Map合同允许拒绝空...

[某些]实现禁止null键和值[...]。尝试插入不合格的键或值会引发未经检查的异常,通常为NullPointerExceptionClassCastException

...,而收集工厂(不仅在地图上)就利用了

它们不允许null键和值。尝试使用null键或值创建它们会导致NullPointerException

但为什么?

null到现在为止,允许集合被视为设计错误。这有多种原因。一个好的方面就是可用性,其中最著名的麻烦制造者是Map::get。如果返回null,则不清楚密钥是丢失还是值是null。一般来说,保证null免费的收藏集更易于使用。在实现方面,它们还需要较少的特殊大小写,从而使代码更易于维护且性能更高。

您可以在本演讲中听Stuart Marks的解释,但JEP 269(介绍工厂方法的人)也对此进行了总结:

空元素,键和值将被禁止。(最近没有引入的集合不支持空值。)此外,禁止空值提供了更紧凑的内部表示,更快的访问权限和更少的特殊情况的机会。

由于HashMap在慢慢发现它的时候就已经泛滥成灾,因此在不破坏现有代码的情况下进行更改为时已晚,但是这些接口的最新实现(例如ConcurrentHashMap)不再允许null了,并且工厂方法的新集合也不例外。

(我认为另一个原因是,明确地使用null值被视为可能的实现错误,但我错了。那将要复制同样也是非法的键。)

因此,不允许使用null某些技术原因,但也可以使用创建的集合来提高代码的健壮性。


5
好吧...我可以想到这种情况:当您在HashMap中有一个具有特定键和值== null的Entry时。您这样做get,它返回null。什么意思 它具有null映射还是不存在?此外,处理空键总是不同的,您无法计算哈希码...不允许以哈希码开头使其更容易工作
Eugene

2
我不会将其视为技术原因而是可用性原因。不过,这是一个好点,所以我将其包括在内。
Nicolai Parlog

6
JEP 269null上具有以下内容:“将不允许使用null元素,键和值。(最近引入的集合均不支持null。)此外,禁止使用null可以提供更紧凑的内部表示,更快的访问和更少的特殊情况。”
Stefan Zobel

2
唯一的可用性原因是get返回null。但是,没有Map使用返回Optional限制的新方法进行扩展,而是实施了这一限制,因为所有处理Map静物的代码都不能依靠不null返回,因此绝对没有解决任何问题。同时,想要传递常量的Map地方恰巧允许null有正当理由允许值的人不能使用此非常方便的功能。
john16384

2
@ john16384 Optional-s的唯一任务是避免显式null处理,并且如果您的建议付诸实践,则对-returning方法的每次调用Optional都必须进行null检查并仍然处理空的情况。那是荒谬的提议。关于运行时意外,您知道,如果您尝试输入键或值,则Map实现可以自由地抛出NPE ,对吗?(是。)到目前为止,您是如何应对的?putnullConcurrentHashMap
Nicolai Parlog

13

没错-一个HashMap允许存储空,没有Map由静态工厂方法返回。并非所有地图都相同。

通常,据我所知,首先要允许在HashMap键中为null的地方有一个错误,但较新的集合禁止这种可能性。

想一想当您的条目中HashMap有一个具有特定键且值== null的情况。你得到了,它返回null。什么意思 它具有的映射null或不存在?

同样的Key-从这样的空键开始的哈希码必须一直被特别对待。禁止以null开头-使其更容易。


是的,它可能被认为是实现中遗留的故障,但是还有许多其他不符合面向对象原理的事情。但是,开发人员经常在hashmap中使用null,并检查是否要初始化业务逻辑中的许多其他内容。
hi.nitish

5
我喜欢您的回答-因为它可以解释为什么部分原因
GhostCat

9

虽然HashMap允许空值,Map.of但不使用a,HashMap并且如果将其用作键或值,则抛出异常,如记录所示

Map.of()和Map.ofEntries()静态工厂方法提供了一种创建不可变地图的便捷方法。这些方法创建的Map实例具有以下特征:

  • ...

  • 它们不允许空键和值。尝试使用空键或空值创建它们会导致NullPointerException。


但是为什么在HashMap中允许它时却不允许呢?
hi.nitish

我认为除Java开发人员外,没有其他人可以可靠地回答为什么
1615903年

5
因为他们意识到在HashMap中允许空值是一个错误。
TimK

7

在地图中允许空值是一个错误。我们现在可以看到它,但是我猜不清楚何时HashMap引入。NullpointerException是生产代码中最常见的错误

我想说的是,JDK朝着帮助开发人员抵抗NPE灾难的方向发展。一些例子:

  • 介绍 Optional
  • Collectors.toMap(keyMapper, valueMapper)不允许既没有keyMapper,也不是valueMapper函数返回一个null
  • Stream.findFirst()Stream.findAny()如果找到的值是NPE并抛出NPEnull

因此,我认为禁止null新的JDK9不可变集合(和地图)的方向相同。


5

主要区别在于:当您以“选项1”方式构建自己的Map时……您在暗中说:“我想在做的事情中拥有完全的自由”。

因此,当您决定自己的地图应具有null键或空值(也许出于此处列出的原因)时,您就可以这样做。

但是“选项2”是关于方便的事情-可能打算用于常量。而Java背后的人们只是简单地决定:“当您使用这些便捷方法时,生成的映射将是无空的”。

允许空意味着

 if (map.contains(key)) 

与...不同

 if (map.get(key) != null)

有时候可能是个问题。或更准确地说:在处理该地图对象时要记住这一点。

还有一个轶事提示为什么这似乎是一种合理的方法:我们的团队自己实施了类似的便捷方法。想一想:在不了解有关未来Java标准方法的计划的情况下,我们的方法完全一样:它们返回输入数据的不变副本;当您提供null元素时,它们就会抛出。我们甚至非常严格,以至于当您传递列表/地图/ ...时,我们也会抱怨。


为什么map.put(key,null)首先对map.contains(key)返回true?
Pedro Borges

3

该文档没有说明为什么 null不允许:

它们不允许null键和值。尝试使用null键或值创建它们会导致NullPointerException

在我看来,将要产生常量的Map.of()Map.ofEntries()静态工厂方法大部分是由开发人员在编译类型下形成的。那么,将a保留null为键或值的原因是什么?

相比之下,Map#put通常是通过在null可能会出现键/值的运行时填充地图来使用。


4
@Eugene如果每个人都阅读(理解)该文档,那么此站点将仅剩一部分问题。
user85421

@CarlosHeuberger将来的jdks中可能有一种趋向于摆脱null的趋势,因此我猜这是原始类型。我读了Map.of()总是返回一个不变的地图,那么null是否与不变性有关?
hi.nitish

3
@ hi.nitish实际上实际上与不变性有关:Map.of()返回其实例ImmutableCollections.MapN使用null必须指示其结束的表。该类的文档:“有一个单个数组“表”,其中*包含交织的键和值...表的大小必须是偶数。它还必须严格大于大小(包含在其中的键-值对的数量)地图),以便始终至少存在一个null键“我不知道为什么要这样做,而不是仅仅使用int来保持大小。可能是哈希...(代码有点吓人
user85421 '17

1
仅仅因为看不到原因并不意味着没有有效的用例。Map例如,用于数据库中字段的具有键/值对的A受益于空值。我想它又回到了NULL占位符对象。恕我直言,原因是小事,或者仅仅是对反零心态的过度反应。
john16384

3

在我看来,不可为空对键有意义,但对值没有意义。

  1. Map接口变为弱合同,你不能没有看落实信任它的行为,这是假设你有机会看到它。Java11Map.of()不允许同时允许null值HashMap,但是它们都实现相同的Map协定-怎么来的?
  2. 不能有空值或根本没有值,并且可以说不应将其视为不同的情况。当您获得键的值而您获得null时,谁在乎它是否被明确放置在其中?提供的键根本没有值,地图不包含这样的键,就这么简单。Null为null,没有nullnull或null ^ 2
  3. 现有的库大量使用map作为将属性传递给它们的方法,其中许多是可选的,这Map.of()对于构造此类结构毫无用处。
  4. Kotlin在编译时强制执行可空性,并允许具有空值的映射而不会发生已知问题。
  5. 不允许使用空值的原因可能仅仅是由于实现细节和创建者的便利。

2

并非所有的Map都允许null为键

提到的原因docs of Map

一些地图实现对它们可能包含的键和值有限制。例如,某些实现禁止空键和空值,而某些实现对其键的类型有限制。尝试插入不合格的键或值会引发未经检查的异常,通常为NullPointerException或ClassCastException。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.