What if I don't want Collectors#toMap
to throw on null values? Java 8
public class CollectorsTest {
@Test
public void collectorsTest() {
List<Map.Entry<String, Object>> params = Arrays.asList(
new AbstractMap.SimpleEntry<>("key1", 1),
new AbstractMap.SimpleEntry<>("key2", null)
);
Map<String, Object> paramMap = toParamMap(params); // throws NPE
}
private static Map<String, Object> toParamMap(List<Map.Entry<String, Object>> params) {
Map<String, Object> paramMap = params.stream().collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
return paramMap;
}
}
What if I don't want Collectors#toMap
to throw on null values? Java 8
public class CollectorsTest {
@Test
public void collectorsTest() {
List<Map.Entry<String, Object>> params = Arrays.asList(
new AbstractMap.SimpleEntry<>("key1", 1),
new AbstractMap.SimpleEntry<>("key2", null)
);
Map<String, Object> paramMap = toParamMap(params); // throws NPE
}
private static Map<String, Object> toParamMap(List<Map.Entry<String, Object>> params) {
Map<String, Object> paramMap = params.stream().collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
return paramMap;
}
}
Share
Improve this question
asked Mar 28 at 11:35
Sergey ZolotarevSergey Zolotarev
1,8691 gold badge11 silver badges31 bronze badges
4
|
2 Answers
Reset to default 6The toMap
collectors don't permit null entry or values. To avoid this you could use forEach
with HashMap.put
as in Hiro Silva's answer.
It is also easy to keep using streams, which might be preferred if making use of parallel or filters on the data. A collector based on HashMap
will permit use of both null keys and values. Stream.collect
handles definition of custom collectors, setting up with arguments to supply a new HashMap
and to add entries to that hashmap:
private static Map<String, Object> toParamMap(List<Map.Entry<String, Object>> params) {
return params.stream()
.collect(HashMap::new,
(map, entry) -> map.put(entry.getKey(), entry.getValue()),
HashMap::putAll);
}
It is cumbersome to write the above each time you might need it, but it can be re-worked to new method returning your own hashmap collector with generic key + value types. This works for handling any collections of Map.Entry using Collector.of
:
public static <K,V> Collector<Map.Entry<K, V>, Map<K, V>, Map<K, V>> toHashMap() {
return Collector.of(HashMap::new,
(map, entry) -> map.put(entry.getKey(), entry.getValue()),
(a,b) -> { a.putAll(b); return a; });
}
Then you can collect streams of Map.Entry
which contains null key+values by adding .collect(toHashMap())
, for example:
Map<String, Object> paramMap = params.stream().collect(toHashMap());
System.out.println(paramMap);
// prints {key1=1, key2=null}
The old HashMap.put()
The modern methods of the standard library, including the methods in the stream package, don’t like nulls as keys or values in maps. Generally for good reasons: nulls are problematic. You may consider a different design where keys with null values are omitted from your map. paramMap.get(yourKey)
will still return the null
that you say you want.
There could still be situations where you got legitimate reasons for wanting one or more null values in the map. To obtain that you may use the classic methods from the introduction of the Java collections framework in Java 1.2. It gets just a little wordier:
private static Map<String, Object> toParamMap(List<Map.Entry<String, Object>> params) {
Map<String, Object> paramMap = new HashMap<>();
params.forEach(param -> paramMap.put(param.getKey(), param.getValue()));
return paramMap;
}
Trying it out with your example:
List<Map.Entry<String, Object>> params = Arrays.asList(
new AbstractMap.SimpleEntry<>("key1", 1),
new AbstractMap.SimpleEntry<>("key2", null)
);
Map<String, Object> paramMap = toParamMap(params);
System.out.println(paramMap);
Output is a map with a null value in it:
{key1=1, key2=null}
HashMap
that throws it onmerge()
. The class is mandated to do so by theMap
interface (seejava.util.Map#merge
) – Sergey Zolotarev Commented Mar 28 at 12:00toMap
collector doesn’t usemerge
. It did in the first version (Java 8) but nowadays uses an explicitnull
check inside the collector to stay compatible to the first version. It would be easy to create a copy of that implementation and remove the explicitnull
check. – Holger Commented Apr 2 at 12:36