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

使用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)的情况下java.util.Set.of("v1", "v2", null)发生同样的情况。

正如其他人指出的, Map合同允许零…

[…] ome实现禁止null键和值[…]。 尝试插入不合格的键或值将引发未检查的exception,通常为NullPointerExceptionClassCastException

…和收集工厂(不只是在地图上) 利用这一点 。

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

但为什么?

在集合中允许null现在被视为devise错误。 这有很多原因。 好的一个是可用性,其中最突出的麻烦制造者是Map::get 。 如果它返回null ,则不清楚该键是否丢失或值为null 。 一般来说,保证null集合更易于使用。 实施方面,他们也需要较less的特殊shell,使代码更容易维护和更高性能。

你可以听斯图尔特·马克斯在这个讲座中解释它,但是JEP 269 (引入工厂方法的那个)也总结了这一点:

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

由于HashMap在慢慢发现的时候就已经出现了,现在修改它已经太晚了,而不会破坏现有的代码,但是这些接口的最新实现(例如ConcurrentHashMap )不再允许null ,并且工厂方法的新集合是没有例外。

(我还以为另一个原因是显式使用null值被认为是一个可能的实现错误,但我得到了错误,那就是要复制密钥,这也是非法的。)

因此,禁止null有一些技术上的原因,但是也使用创build的集合来提高代码的健壮性。

虽然HashMap确实允许空值,但Map.of不会使用HashMap并且如果使用key或value,则会引发exception,如文档所述 :

Map.of()和Map.ofEntries()静态工厂方法提供了创build不可变映射的便捷方式。 这些方法创build的Map实例具有以下特征:

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

确切地说 – 一个HashMap被允许存储null, 而不是静态工厂方法返回的Map 。 并不是所有的地图都是一样的。

一般来说,据我所知,首先允许HashMap空值作为键是错误的,但新的集合禁止了这种可能性。

想象一下当你的HashMap中有一个具有特定键和值== null的Entry的情况。 你确实得到了,它返回null 。 这是什么意思? 它有一个null的映射或它不存在?

Key – 从这样一个空密钥的哈希代码必须特别对待所有的时间。 禁用空位开始 – 使这更容易。

主要区别在于:当你build立你自己的地图的时候,你会隐含地说:“我想在我所做的事情上有充分的自由 ”。

所以,当你决定你的地图应该有一个空的键或值(可能是由于这里列出的原因),那么你可以自由地这样做。

但“选项2”是关于一个方便的东西 – 可能打算用于常量。 Java背后的人只是简单地决定:“当你使用这些方便的方法时,那么结果图就是无空的”。

允许空意味着

  if (map.contains(key)) 

是不一样的

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

这有时可能是一个问题。 或者更确切地说:当处理那个非常地图的对象的时候,这是要记住的。

只是一个轶事暗示为什么这似乎是一个合理的方法:我们的团队自己实施类似的便利方法。 猜测一下:如果不知道关于未来Java标准方法的计划,我们的方法会做同样的事情:它们会返回一个不可变的传入数据副本; 当你提供null元素时,他们会抛出。 我们甚至非常严格,以至于当你通过空的名单/地图/ …我们也抱怨。

并非所有地图都允许null作为关键字

docs of Map提到的原因。

一些映射实现对它们可能包含的键和值有限制。 例如,一些实现禁止空键和值,一些实现对键的types有限制。 尝试插入不合格的键或值将引发未检查的exception,通常为NullPointerException或ClassCastException。

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

我可能会说JDK正在帮助开发者打击NPE瘟疫。 一些例子:

  • Optional介绍
  • Collectors.toMap(keyMapper, valueMapper)既不允许keyMapper也不允许valueMapper函数返回null
  • 如果find的值为nullStream.findFirst()Stream.findAny()抛出NPE

所以,在新的JDK9不可变集合(和map)中不允许null只是沿着相同的方向。 我们都应该感谢它!

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

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

在我看来, Map.of()Map.ofEntries()静态工厂方法将产生一个常量,大多由开发人员在编译types中形成。 那么,为什么要保留null作为关键或价值呢?

然而, Map#put通常是通过在运行时填充一个映射来使用的,其中null键/值可能发生。