[英]Why does Map.of not allow null keys and values?
在 Java 9 中,为List
、 Set
和Map
接口引入了新的工厂方法。 这些方法允许使用一行中的值快速实例化 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)
。
正如其他人指出的那样, Map
合约允许拒绝空值......
[S] 某些实现禁止
null
键和值 [...]。 尝试插入不合格的键或值会引发未经检查的异常,通常为NullPointerException
或ClassCastException
。
...和收集工厂(不仅仅是在地图上) 利用了那个。
它们不允许
null
键和值。 尝试使用null
键或值创建它们会导致NullPointerException
。
在集合中允许null
现在被视为设计错误。 这有多种原因。 一个好的是可用性,其中最突出的麻烦制造者是Map::get
。 如果它返回null
,则不清楚是缺少键还是值为null
。 一般来说,保证null
集合更容易使用。 在实现方面,它们还需要较少的特殊外壳,使代码更易于维护和性能更高。
你可以听 Stuart Marks在这个演讲中解释它,但JEP 269 (介绍工厂方法的那个)也总结了它:
将不允许使用空元素、键和值。 (最近引入的集合都没有支持空值。)此外,禁止空值为更紧凑的内部表示、更快的访问和更少的特殊情况提供了机会。
由于HashMap
在慢慢被发现时已经被广泛使用,因此在不破坏现有代码的情况下对其进行更改为时已晚,但是这些接口的最新实现(例如ConcurrentHashMap
)不再允许null
并且工厂方法的新集合是也不例外。
(我认为另一个原因是显式使用null
值被视为可能的实现错误,但我错了。这将复制密钥,这也是非法的。)
所以禁止null
有一些技术原因,但这样做也是为了使用创建的集合提高代码的健壮性。
确切地说 - HashMap
允许存储 null,而不是静态工厂方法返回的Map
。 并非所有地图都相同。
一般来说,据我所知,首先允许HashMap
空值作为键是错误的,较新的集合禁止这种可能性开始。
想想你的HashMap
中有一个条目,它有一个特定的键和值 == 空的情况。 你确实得到了,它返回null
。 这是什么意思? 它有一个null
映射还是不存在?
Key
——来自这样一个空密钥的哈希码必须一直被特殊处理。 禁止空值开始 - 让这更容易。
虽然 HashMap 确实允许空值,但Map.of
不使用HashMap
并且如果将其中一个用作键或值,则会引发异常,如 文档所示:
Map.of() 和 Map.ofEntries() 静态工厂方法提供了一种创建不可变映射的便捷方法。 这些方法创建的 Map 实例具有以下特点:
...
它们不允许空键和值。 尝试使用空键或值创建它们会导致 NullPointerException。
在我看来,不可空性对键有意义,但对值则不然。
Map
接口变成了一个弱契约,你不能在不查看实现的情况下相信它的行为,那就是假设你有权查看它。 Java11 的Map.of()
不允许空值,而HashMap
允许,但它们都实现了相同的Map
契约 - 为什么?Map.of()
作为此类结构的构造毫无用处。主要区别在于:当您以“选项 1”的方式构建自己的 Map 时……那么您就含蓄地说:“我希望在我正在做的事情上拥有完全的自由”。
因此,当您决定您的地图应该有一个空键或值(可能是出于此处列出的原因)时,您可以自由地这样做。
但是“选项 2”是关于方便的事情 - 可能旨在用于常量。 Java 背后的人简单地决定:“当你使用这些方便的方法时,结果映射应该是空的”。
允许空值意味着
if (map.contains(key))
不一样
if (map.get(key) != null)
有时这可能是一个问题。 或者更准确地说:这是在处理那个地图对象时要记住的事情。
这只是一个轶事提示,为什么这似乎是一种合理的方法:我们的团队自己实现了类似的便利方法。 猜猜看:在对未来 Java 标准方法的计划一无所知的情况下 - 我们的方法做完全相同的事情:它们返回传入数据的不可变副本; 当您提供 null 元素时,它们会抛出。 我们甚至非常严格,当您传入空列表/地图/...时,我们也会抱怨。
在地图中允许空值是一个错误。 我们现在可以看到它,但我想不清楚什么时候引入HashMap
。 NullpointerException
是生产代码中最常见的错误。
我会说 JDK 朝着帮助开发人员对抗 NPE 瘟疫的方向发展。 一些例子:
Optional
介绍Collectors.toMap(keyMapper, valueMapper)
不允许keyMapper
和valueMapper
函数返回null
值null
Stream.findFirst()
和Stream.findAny()
将抛出 NPE 所以,我认为在新的 JDK9 不可变集合(和映射)中禁止null
朝着相同的方向发展。
文档没有说明为什么不允许使用null
:
它们不允许
null
键和值。 尝试使用null
键或值创建它们会导致NullPointerException
。
在我看来,将Map.of()
和Map.ofEntries()
静态工厂方法主要由编译类型的开发人员形成。 那么,保留null
作为键或值的原因是什么?
而Map#put
通常用于在运行时填充可能出现null
键/值的映射。
并非所有 Map 都允许 null 作为键
docs of Map
提到的原因。
一些地图实现对它们可能包含的键和值有限制。 例如,有些实现禁止空键和值,有些实现对其键的类型有限制。 尝试插入不合格的键或值会引发未经检查的异常,通常为 NullPointerException 或 ClassCastException。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.