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. These methods allow quickly instantiating a Map object with values in one line. 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.
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?
The same thing happens in the case of java.util.Set.of("v1", "v2", null)
and java.util.List.of("v1", "v2", null)
.
As others pointed out, the Map
contract allows rejecting nulls...
[S]ome implementations prohibit
null
keys and values [...]. Attempting to insert an ineligible key or value throws an unchecked exception, typicallyNullPointerException
orClassCastException
.
... and the collection factories (not just on maps) make use of that.
They disallow
null
keys and values. Attempts to create them withnull
keys or values result inNullPointerException
.
But why?
Allowing null
in collections is by now seen as a design error. This has a variety of reasons. A good one is usability, where the most prominent trouble maker is Map::get
. If it returns null
, it is unclear whether the key is missing or the value was null
. Generally speaking, collections that are guaranteed null
free are easier to use. 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:
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 (e.g. ConcurrentHashMap
) do not allow null
anymore and the new collections for the factory methods are no exception.
(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.)
So disallowing null
had some technical reason but it was also done to improve the robustness of the code using the created collections.
Exactly - a HashMap
is allowed to store null, not the Map
returned by the static factory methods. 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.
Think of the case when you have an Entry in your HashMap
that has a certain key and value == null. You do get, it returns null
. What does the mean? It has a mapping of null
or it is not present?
Same goes for a Key
- hashcode from such a null key has to treated specially all the time. Banning nulls to start with - make this easier.