admin管理员组

文章数量:1356285

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
  • 4 You can’t. You would need to write your own collector. Which is definitely possible, said without having done it myself. – Anonymous Commented Mar 28 at 11:40
  • @Anonymous it's HashMap that throws it on merge(). The class is mandated to do so by the Map interface (see java.util.Map#merge) – Sergey Zolotarev Commented Mar 28 at 12:00
  • That holds true with the implementation in my OpenJDK Java 23 too. It may be different in other Java versions. – Anonymous Commented Mar 28 at 12:20
  • @SergeyZolotarev the toMap collector doesn’t use merge. It did in the first version (Java 8) but nowadays uses an explicit null 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 explicit null check. – Holger Commented Apr 2 at 12:36
Add a comment  | 

2 Answers 2

Reset to default 6

The 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}

本文标签: javaCollectorstoMap No NPEs on null valuesStack Overflow