关于Stream转Map的Duplicate key异常处理

1.问题复现

先初始化一个 School 的集合,然后将该集合转成一个 Map,key 为 id, value 为 name。
:学校的 id 设置为重复的。
上代码

public static void main(String[] args) {
        List<School> schoolList = Lists.newArrayList(
                new School("1", "a"),
                new School("2", "b"),
                new School("1", "c")
        );
        Map<String, School> schoolMap = schoolList.stream().collect(Collectors.toMap(School::getId, dic -> dic));
        System.out.println(schoolMap);
    }

    @Data
    @AllArgsConstructor
    static class School implements Serializable {
        private String id;
        private String name;
    }

运行以上代码,控制台会出现以下错误:

Exception in thread "main" java.lang.IllegalStateException: Duplicate key StreamTest.School(id=1, name=a)
    at java.util.stream.Collectors.lambda$throwingMerger$0(Collectors.java:133)
    at java.util.HashMap.merge(HashMap.java:1245)
    at java.util.stream.Collectors.lambda$toMap$58(Collectors.java:1320)
    at java.util.stream.ReduceOps$3ReducingSink.accept(ReduceOps.java:169)
    at java.util.ArrayList$ArrayListSpliterator.forEachRemaining(ArrayList.java:1374)
    at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:481)
    at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:471)
    at java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:708)
    at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
    at java.util.stream.ReferencePipeline.collect(ReferencePipeline.java:499)
    at com.iflytek.edu.udp.web.admin.manager.StreamTest.main(StreamTest.java:24)

原因就是转换为 map 的时候 key 重复了。

2.解决方案

对于以上问题,是因为我们使用的方法不对,java8 的 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)

public static <T, K, U> Collector<T, ?, Map<K,U>> toMap(Function<? super T, ? extends K> keyMapper,
                                    Function<? super T, ? extends U> valueMapper,
                                    BinaryOperator<U> mergeFunction) 

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> mapSupplier) 

上面的代码使用的就是第一个,我们看下该方法的注释就明白了:

返回一个将元素累积到 Map 中的收集器,其键和值是将提供的映射函数应用于输入元素的结果。
如果映射的键包含重复项(根据 Object.equals(Object)),则在执行集合操作时会抛出 IllegalStateException。 如果映射的键可能有重复项,请改用 toMap(Function, Function, BinaryOperator)。

所以答案就在里面,如果有映射的键有重复项就会报错,我们应该使用第二个方法,所以上面的代码我们略作修改即可,当碰到相同的键时,用后面的 value 覆盖前面的。

Map<String, School> schoolMap = schoolList.stream().collect(Collectors.toMap(School::getId, dic -> dic, (oldValue, newValue) -> newValue));

代码跑起来,打印结果如下:

{1=StreamTest.School(id=1, name=c), 2=StreamTest.School(id=2, name=b)}
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容