解决Java `Collectors.toMap` 方法 value为null不支持的问题

解决Java Collectors.toMap 方法 value为null不支持的问题

问题描述

在需要把List转换为Map时,通常会使用到Collectors.toMap 方法来转换,但如果转换后的Map的Value有null 则执行时会抛出NullPointerException 异常。

演示代码:

   static class Data{
        int key;
        String value;

        public Data(int key, String value) {
            this.key = key;
            this.value = value;
        }
    }

  @Test
    public void test_toMap(){
        List<Data> dataList = List.of(
            new Data(1,"A"),
            new Data(2,"B"),
            new Data(3,"C"),
            new Data(4,null),
            new Data(5,null)
            );
        Map<Integer,String> dataMap = dataList.stream().collect(Collectors.toMap(item->item.key,item->item.value));

    }

执行的异常内容:

java.lang.NullPointerException
	at java.base/java.util.Objects.requireNonNull(Objects.java:221)
	at java.base/java.util.stream.Collectors.lambda$uniqKeysMapAccumulator$1(Collectors.java:176)
	at java.base/java.util.stream.ReduceOps$3ReducingSink.accept(ReduceOps.java:169)
	at java.base/java.util.AbstractList$RandomAccessSpliterator.forEachRemaining(AbstractList.java:720)
	at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:484)
	at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:474)
	at java.base/java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:913)
	at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
	at java.base/java.util.stream.ReferencePipeline.collect(ReferencePipeline.java:511)

原因

Collectors.toMap 有实际有两个实现

  • 第一个实现
public static <T, K, U>
    Collector<T, ?, Map<K,U>> toMap(Function<? super T, ? extends K> keyMapper,
                                    Function<? super T, ? extends U> valueMapper) {
        return new CollectorImpl<>(HashMap::new,
                                   uniqKeysMapAccumulator(keyMapper, valueMapper),
                                   uniqKeysMapMerger(),
                                   CH_ID);
    }

  • 第二个实现
 public static <T, K, U, M extends Map<K, U>>
    Collector<T, ?, M> toMap(Function<? super T, ? extends K> keyMapper,
                             Function<? super T, ? extends U> valueMapper,
                             BinaryOperator<U> mergeFunction,
                             Supplier<M> mapFactory) {
        BiConsumer<M, T> accumulator
                = (map, element) -> map.merge(keyMapper.apply(element),
                                              valueMapper.apply(element), mergeFunction);
        return new CollectorImpl<>(mapFactory, accumulator, mapMerger(mergeFunction), CH_ID);
    }

其中第一个实现,会在uniqKeysMapAccumulator(keyMapper, valueMapper)方法中通过Objects.requireNonNull(valueMapper.apply(element)) 控制value不能为null。

而第二个实现,最终会会调用Map.merge方法来合并value,这个方法里也会通过Objects.requireNonNull(value);来控制value不能为null。

解决方案

因此Collectors.toMap 方法是无法解决value为null 抛出异常的,但我们实现list转map的需求还是存在的,那么对于这类需求,可以选择另一种方式实现,就是用Collectors.groupingBy

实现方案 1

默认的Collectors.groupingBy 返回的是Map> 形式的结果,但我们需要返回的Map形式的结果。因此需要对需要做额外的一些处理。

先给出结果demo

    @Test
    public void test_groupByMap(){
        List<Data> dataList = List.of(
            new Data(1,"A"),
            new Data(2,"B"),
            new Data(3,"C"),
            new Data(4,null),
            new Data(5,null)
        );
        Map<Integer,String> dataMap = dataList.stream()
            .collect(Collectors.groupingBy(item->item.key,
                HashMap::new,
                Collectors.reducing(null,item->item.value,(v1,v2)->v2)
                )
            );

    }

原理就是利用额外的Collectors.reducing来做收集处理,将最终的List 转换成 Value

实现方案 2

学到另一种方案,调用Stream的另一个collect方法。

<R> R collect(Supplier<R> supplier,
                  BiConsumer<R, ? super T> accumulator,
                  BiConsumer<R, R> combiner);
public void test_toMap4(){
        List<Data> dataList = List.of(
            new Data(1,"A"),
            new Data(2,"B"),
            new Data(3,"C"),
            new Data(4,null),
            new Data(5,null)
        );
        Map<Integer,String> dataMap = dataList.stream()
            .collect(HashMap::new,(map,item)->map.put(item.key,item.value),(map1,map2)->map1.putAll(map2)
            );
    }

当然(map1,map2)->map1.putAll(map2) 也可以简化成HashMap::putAll

你可能感兴趣的:(我的分享,java,开发语言)