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

Java 9 introduced new factory methods for the List , Set and Map interfaces. These methods allow you to quickly instantiate a Map object with values ​​on a single line. Now, considering:

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

The above is permitted without any exceptions if we do:

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

He throws out:

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

I cannot get, why null is not allowed in the second case.

I know that HashMap can take null as a key, as well as a value, but why was this limited in the case of Map.of?

The same thing happens with java.util.Set.of("v1", "v2", null) and java.util.List.of("v1", "v2", null) .

+45
java java-9
Jul 20 '17 at 9:16
source share
7 answers

As others noted, the Map contract allows you to reject zeros ...

[S] ome implementations prohibit null keys and values ​​[...]. Attempting to insert an inappropriate key or value throws an exception, usually ClassCastException NullPointerException or ClassCastException .

... and collectors (and not just on maps) use this .

They prohibit keys and null values. Attempting to create them using null keys or values ​​will result in a NullPointerException .

But why?

null permission in collections is now considered a design error. This has many reasons. Ease of use is good, where the most notable problem creator is Map::get . If it returns null , it is not clear if the key or null is missing. Generally speaking, collections guaranteed by null for free are easier to use. Implementation, they also require fewer special enclosures, which makes the code more convenient to maintain and more efficient.

You can listen to Stuart Marks, explain this in this conversation , but JEP 269 (the one that introduced the factory methods) also summarizes it:

Invalid items, keys, and values ​​will be prohibited. (The newly created collections did not support zeros.) In addition, the prohibition of zeros provides opportunities for a more compact internal representation, faster access, and fewer special cases.

Since the HashMap had already come to a standstill when it was slowly discovered, it was too late to change it without breaking existing code, and the most recent implementations of these interfaces (for example, ConcurrentHashMap ) no longer allow null , and new collections for factory methods are not an exception.

(I thought the other reason was that explicitly using null values ​​was seen as a likely implementation error, but I was wrong. It was like duplicating keys that are also illegal.)

So the rejection of null had some technical reasons, but it was also made to increase the reliability of the code using the created collections.

+53
Jul 20 '17 at 9:26
source share

Exactly - a HashMap allowed to store the null value, not the Map returned by the static factory methods. Not all cards are the same.

In general, as far as I know, firstly, the error allows zeros in HashMap keys, but new collections forbid this feature.

Think about the case when you have an entry in your HashMap that has a specific key and value == null. You get, it returns null . What means? Does it have a null mapping or is it not?

The same applies to Key - the hash code from such a zero key must be specially processed all the time. Deny zeros to begin with - make it easier.

+8
Jul 20 '17 at 9:20
source share

While HashMap allows null values, Map.of does not use HashMap and throws an exception if it is used as a key or value, as documented :

The Map.of () and Map.ofEntries () static factory methods provide a convenient way to create immutable maps. Map instances created by these methods have the following characteristics:

  • ...

  • They prohibit null keys and values. Attempts to create them using null keys or values ​​result in a NullPointerException.

+8
Jul 20. '17 at 9:21
source share

The main difference is this: when you create your own map "option 1" ... then you implicitly say: "I want to have complete freedom in what I do."

So, when you decide that your card should have a null key or value (perhaps for the reasons listed here below), then you can do it.

But "option 2" is a convenient thing - probably intended for use in constants. And the people behind Java just decided: "when you use these convenient methods, then the resulting map will be null."

Zero resolution means that

  if (map.contains(key)) 

does not match

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

which can sometimes be a problem. More precisely: this is what you need to remember when working with this very object of the map.

And just an anecdotal hint on why this seems like a reasonable approach: our team itself applied similar convenience methods. And guess what: without knowing anything about the plans for future standard Java methods - our methods do the same: they return an immutable copy of the incoming data; and they call when you provide null elements. We are even so strict that when you go to empty lists / cards / ... we also complain.

+5
Jul 20 '17 at 13:29
source share

Not all cards accept null as a key

The reason mentioned in the docs of Map .

Some card implementations have restrictions on the keys and the values ​​that they may contain. For example, some implementations prohibit empty keys and values, and some have restrictions on the types of their keys. Attempting to insert an invalid key or value throws an exception that throws an exception, usually NullPointerException or ClassCastException.

+3
Jul 20 '17 at 9:20
source share

Resolving zeros on maps was a mistake. We can see it now, but I think it was unclear when the HashMap was introduced. NullpointerException is the most common error seen in production code.

I can say that the JDK is going to help developers fight the NPE plague. Some examples:

  • Optional Introduction
  • Collectors.toMap(keyMapper, valueMapper) do not allow either the keyMapper function or valueMapper to return null
  • Stream.findFirst() and Stream.findAny() throw NPE if found null

So, the null ban on new immutable collections (and the map) of JDK9 just goes in one direction. And we all have to thank him!

+3
Jul 20 '17 at 18:59
source share

The documentation does not indicate why null not allowed:

They prohibit null keys and values. Attempting to create them using null keys or values ​​will result in a NullPointerException .

In my opinion, the methods Map.of() and Map.ofEntries() static factory, which are going to create a constant, are mainly formed by the developer during compilation. Then, what is the reason for keeping null as a key or value?

Whereas Map#put usually used by populating a map at runtime, where null keys / values ​​may occur.

+1
Jul 20 '17 at 9:28
source share



All Articles