简体   繁体   English

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

[英]Why does Map.of not allow null keys and values?

With Java 9, new factory methods have been introduced for the List , Set and Map interfaces.在 Java 9 中,为ListSetMap接口引入了新的工厂方法。 These methods allow quickly instantiating a Map object with values in one line.这些方法允许使用一行中的值快速实例化 Map 对象。 Now, if we consider:现在,如果我们考虑:

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

The above is allowed without any exception while if we do:以上是允许的,没有任何例外,而如果我们这样做:

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

It throws:它抛出:

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

I am not able to get, why null is not allowed in second case.我无法得到,为什么在第二种情况下不允许 null

I know HashMap can take null as a key as well as a value but why was that restricted in the case of Map.of?我知道 HashMap 可以将 null 作为键和值,但为什么在 Map.of 的情况下会受到限制?

The same thing happens in the case of java.util.Set.of("v1", "v2", null) and java.util.List.of("v1", "v2", null) .同样的事情发生在java.util.Set.of("v1", "v2", null)java.util.List.of("v1", "v2", null)

As others pointed out, the Map contract allows rejecting nulls...正如其他人指出的那样, Map合约允许拒绝空值......

[S]ome implementations prohibit null keys and values [...]. [S] 某些实现禁止null键和值 [...]。 Attempting to insert an ineligible key or value throws an unchecked exception, typically NullPointerException or ClassCastException .尝试插入不合格的键或值会引发未经检查的异常,通常为NullPointerExceptionClassCastException

... and the collection factories (not just on maps) make use of that . ...和收集工厂(不仅仅是在地图上) 利用了那个

They disallow null keys and values.它们不允许null键和值。 Attempts to create them with null keys or values result in NullPointerException .尝试使用null键或值创建它们会导致NullPointerException

But why?但为什么?

Allowing null in collections is by now seen as a design error.在集合中允许null现在被视为设计错误。 This has a variety of reasons.这有多种原因。 A good one is usability, where the most prominent trouble maker is Map::get .一个好的是可用性,其中最突出的麻烦制造者是Map::get If it returns null , it is unclear whether the key is missing or the value was null .如果它返回null ,则不清楚是缺少键还是值为null Generally speaking, collections that are guaranteed null free are easier to use.一般来说,保证null集合更容易使用。 Implementation-wise, they also require less special casing, making the code easier to maintain and more performant.在实现方面,它们还需要较少的特殊外壳,使代码更易于维护和性能更高。

You can listen to Stuart Marks explain it in this talk but JEP 269 (the one that introduced the factory methods) summarizes it as well:你可以听 Stuart Marks在这个演讲中解释它JEP 269 (介绍工厂方法的那个)也总结了它:

Null elements, keys, and values will be disallowed.将不允许使用空元素、键和值。 (No recently introduced collections have supported nulls.) In addition, prohibiting nulls offers opportunities for a more compact internal representation, faster access, and fewer special cases. (最近引入的集合都没有支持空值。)此外,禁止空值为更紧凑的内部表示、更快的访问和更少的特殊情况提供了机会。

Since HashMap was already out in the wild when this was slowly discovered, it was too late to change it without breaking existing code but most recent implementations of those interfaces (eg ConcurrentHashMap ) do not allow null anymore and the new collections for the factory methods are no exception.由于HashMap在慢慢被发现时已经被广泛使用,因此在不破坏现有代码的情况下对其进行更改为时已晚,但是这些接口的最新实现(例如ConcurrentHashMap )不再允许null并且工厂方法的新集合是也不例外。

(I thought another reason was that explicitly using null values was seen as a likely implementation error but I got that wrong. That was about to duplicate keys, which are illegal as well.) (我认为另一个原因是显式使用null值被视为可能的实现错误,但我错了。这将复制密钥,这也是非法的。)

So disallowing null had some technical reason but it was also done to improve the robustness of the code using the created collections.所以禁止null有一些技术原因,但这样做也是为了使用创建的集合提高代码的健壮性。

Exactly - a HashMap is allowed to store null, not the Map returned by the static factory methods.确切地说 - HashMap允许存储 null,而不是静态工厂方法返回的Map Not all maps are the same.并非所有地图都相同。

Generally as far as I know it has a mistake in the first place to allow nulls in the HashMap as keys, newer collections ban that possibility to start with.一般来说,据我所知,首先允许HashMap空值作为键是错误的,较新的集合禁止这种可能性开始。

Think of the case when you have an Entry in your HashMap that has a certain key and value == null.想想你的HashMap中有一个条目,它有一个特定的键和值 == 空的情况。 You do get, it returns null .你确实得到了,它返回null What does the mean?这是什么意思? It has a mapping of null or it is not present?它有一个null映射还是不存在?

Same goes for a Key - hashcode from such a null key has to treated specially all the time. Key ——来自这样一个空密钥的哈希码必须一直被特殊处理。 Banning nulls to start with - make this easier.禁止空值开始 - 让这更容易。

While HashMap does allow null values, Map.of does not use a HashMap and throws an exception if one is used either as key or value, as documented :虽然 HashMap 确实允许空值,但Map.of不使用HashMap并且如果将其中一个用作键或值,则会引发异常,如 文档所示

The Map.of() and Map.ofEntries() static factory methods provide a convenient way to create immutable maps. Map.of() 和 Map.ofEntries() 静态工厂方法提供了一种创建不可变映射的便捷方法。 The Map instances created by these methods have the following characteristics:这些方法创建的 Map 实例具有以下特点:

  • ... ...

  • They disallow null keys and values.它们不允许空键和值。 Attempts to create them with null keys or values result in NullPointerException.尝试使用空键或值创建它们会导致 NullPointerException。

In my opinion non-nullability makes sense for keys, but not for values.在我看来,不可空性对键有意义,但对值则不然。

  1. The Map interface becomes a weak contract, you can't trust it's behaviour without looking at the implementation, and that is assuming you have access to see it. Map接口变成了一个弱契约,你不能在不查看实现的情况下相信它的行为,那就是假设你有权查看它。 Java11's Map.of() does not allow null values while HashMap does, but they both implement the same Map contract - how come? Java11 的Map.of()不允许空值,而HashMap允许,但它们都实现了相同的Map契约 - 为什么?
  2. Having a null value or no value at all could not, and arguably should not be considered as a distinct scenario.有一个空值或根本没有值是不可能的,并且可以说不应该被视为一个不同的场景。 When you get the value for a key and you get null, who cares if it was explicitly put there or not?当你得到一个键的值并且你得到空值时,谁在乎它是否明确地放在那里? there's simply no value for the supplied key, the map does not contain such key, simple as that.提供的键根本没有值,地图不包含这样的键,就这么简单。 Null is null, there's no nullnull or null^2 Null 为 null,没有 nullnull 或 null^2
  3. Existing libraries make extensive use of map as a means to pass properties to them, many of them optional, this makes Map.of() useless as a construct for such structures.现有的库广泛使用 map 作为向它们传递属性的一种手段,其中许多是可选的,这使得Map.of()作为此类结构的构造毫无用处。
  4. Kotlin enforces nullability at compile time and allows for maps with null values with no known issues. Kotlin 在编译时强制执行可空性,并允许使用没有已知问题的空值映射。
  5. The reason behind not allowing null values is probably just due to implementation detail and convenience of the creator.不允许空值背后的原因可能只是由于创建者的实现细节和便利性。

The major difference is: when you build your own Map the "option 1" way ... then you are implicitly saying: "I want to have full freedom in what I am doing".主要区别在于:当您以“选项 1”的方式构建自己的 Map 时……那么您就含蓄地说:“我希望在我正在做的事情上拥有完全的自由”。

So, when you decide that your map should have a null key or value (maybe for the reasons listed here ) then you are free to do so.因此,当您决定您的地图应该有一个空键或值(可能是出于此处列出的原因)时,您可以自由地这样做。

But "option 2" is about a convenience thing - probably intended to be used for constants.但是“选项 2”是关于方便的事情 - 可能旨在用于常量。 And the people behind Java simply decided: "when you use these convenience methods, then the resulting map shall be null-free". Java 背后的人简单地决定:“当你使用这些方便的方法时,结果映射应该是空的”。

Allowing for null values means that允许空意味着

 if (map.contains(key)) 

is not the same as不一样

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

which might be a problem sometimes.有时这可能是一个问题。 Or more precisely: it is something to remember when dealing with that very map object.或者更准确地说:这是在处理那个地图对象时要记住的事情。

And just an anecdotal hint why this seems to be a reasonable approach: our team implemented similar convenience methods ourselves.这只是一个轶事提示,为什么这似乎是一种合理的方法:我们的团队自己实现了类似的便利方法。 And guess what: without knowing anything about plans about future Java standard methods - our methods do the exact same thing: they return an immutable copy of the incoming data;猜猜看:在对未来 Java 标准方法的计划一无所知的情况下 - 我们的方法做完全相同的事情:它们返回传入数据的不可变副本; and they throw up when you provide null elements.当您提供 null 元素时,它们会抛出。 We are even so strict that when you pass in empty lists/maps/... we complain as well.我们甚至非常严格,当您传入列表/地图/...时,我们也会抱怨。

Allowing nulls in maps has been an error.在地图中允许空值是一个错误。 We can see it now , but I guess it wasn't clear when HashMap was introduced.我们现在可以看到它,但我想不清楚什么时候引入HashMap NullpointerException is the most common bug seen in production code. NullpointerException是生产代码中最常见的错误

I would say that the JDK goes in the direction of helping developers fight the NPE plague.我会说 JDK 朝着帮助开发人员对抗 NPE 瘟疫的方向发展。 Some examples:一些例子:

  • Introduction of Optional Optional介绍
  • Collectors.toMap(keyMapper, valueMapper) doesn't allow neither the keyMapper nor the valueMapper function to return a null value Collectors.toMap(keyMapper, valueMapper)不允许keyMappervalueMapper函数返回null
  • Stream.findFirst() and Stream.findAny() throw NPE if the value found is null如果找到的值为null Stream.findFirst()Stream.findAny()将抛出 NPE

So, I think that disallowing null in the new JDK9 immutable collections (and maps) goes in the same direction.所以,我认为在新的 JDK9 不可变集合(和映射)中禁止null朝着相同的方向发展。

The documentation does not say why null is not allowed:文档没有说明为什么不允许使用null

They disallow null keys and values.它们不允许null键和值。 Attempts to create them with null keys or values result in NullPointerException .尝试使用null键或值创建它们会导致NullPointerException

In my opinion, the Map.of() and Map.ofEntries() static factory methods, which are going to produce a constant, are mostly formed by a developer at the compile type.在我看来,将Map.of()Map.ofEntries()静态工厂方法主要由编译类型的开发人员形成。 Then, what is the reason to keep a null as the key or the value?那么,保留null作为键或值的原因是什么?

Whereas, the Map#put is usually used by filling a map at runtime where null keys/values could occur.Map#put通常用于在运行时填充可能出现null键/值的映射。

Not all Maps allow null as key并非所有 Map 都允许 null 作为键

The reason mentioned in the docs of Map . docs of 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.尝试插入不合格的键或值会引发未经检查的异常,通常为 NullPointerException 或 ClassCastException。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM