Collectors.toMap 使用技巧 (List 转 Map超方便)

前言

  1. 过去的做法(List 转 Map)

    List<User> userList = new ArrayList<>();
    userList.add(new User().setId("A").setName("张三"));
    userList.add(new User().setId("B").setName("李四"));
    userList.add(new User().setId("C").setName("王五"));
    //遍历
    Map<String, String> map = new HashMap<>();
    for (User user : userList) {
        map.put(user.getId(), user.getName());
    }
    
  2. 如今我们可以利用Java8 中新增的 Stream 特性,使得操作集合更加的方便
    使用Collectors.toMap来代替之前的手写遍历,但是在阅读Java开发手册(嵩山版)后,发现了一个容易被忽视的隐患!
    原文如下:
    【强制】在使用java.util.stream.Collectors 类的 toMap()方法转为 Map 集合时,一定要使用含有参数类型为BinaryOperator,参数名为mergeFunction 的方法,否则当出现相同key值时会抛出 IllegalStateException 异常。
    说明:参数 mergeFunction 的作用是当出现 key 重复时,自定义对value 的处理策略。

正例:

List<Pair<String, Double>> pairArrayList = new ArrayList<>(3);
pairArrayList.add(new Pair<>("version", 12.10));
pairArrayList.add(new Pair<>("version", 12.19));
pairArrayList.add(new Pair<>("version", 6.28));
Map<String, Double> map = pairArrayList.stream().collect(
// 生成的 map 集合中只有一个键值对:{version=6.28}
Collectors.toMap(Pair::getKey, Pair::getValue, (v1, v2) -> v2));

反例:

String[] departments = new String[] {"iERP", "iERP", "EIBU"};
// 抛出 IllegalStateException 异常
Map<Integer, String> map = Arrays.stream(departments)
.collect(Collectors.toMap(String::hashCode, str -> str));

一、Collectors.toMap的详细使用技巧

  • 开篇遍历的做法, 我们可以一行代码搞定:

    userList.stream().collect(Collectors.toMap(User::getId, User::getName));
    
  • 当然,如果希望得到 Mapvalue为对象本身时,可以这样写:

    userList.stream().collect(Collectors.toMap(User::getId, t -> t));
    或者:
    userList.stream().collect(Collectors.toMap(User::getId, Function.identity()));	
    

关于 Collectors.toMap 方法

  • Collectors.toMap有三个重载方法:

    toMap(Function<? super T, ? extends K> keyMapper, Function<? super T, ? extends U> valueMapper);
    toMap(Function<? super T, ? extends K> keyMapper, Function<? super T, ? extends U> valueMapper,
            BinaryOperator<U> mergeFunction);
    toMap(Function<? super T, ? extends K> keyMapper, Function<? super T, ? extends U> valueMapper,
            BinaryOperator<U> mergeFunction, Supplier<M> mapSupplier);
    

    参数解释:

    1. keyMapperKey 的映射函数
    2. valueMapperValue的映射函数
    3. mergeFunction:当 Key 冲突时,调用的合并方法
    4. mapSupplierMap 构造器,在需要返回特定的 Map 时使用

    还是用上面的例子,如果 List 中 userId 有相同的,使用上面的写法会抛异常:

    List<User> userList = new ArrayList<>();
    userList.add(new User().setId("A").setName("张三"));
    userList.add(new User().setId("A").setName("李四"));//相同的key
    userList.add(new User().setId("C").setName("王五"));
    userList.stream().collect(Collectors.toMap(User::getId, User::getName));
    
    // 异常:
    java.lang.IllegalStateException: Duplicate key 张三 
        at java.util.stream.Collectors.lambda$throwingMerger$114(Collectors.java:133)
        at java.util.HashMap.merge(HashMap.java:1245)
        at java.util.stream.Collectors.lambda$toMap$172(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 Test.toMap(Test.java:17)
        ...
    

    这时就需要调用第二个重载方法,传入合并函数,如:

    userList.stream().collect(Collectors.toMap(User::getId, User::getName, (n1, n2) -> n1 + n2));
    
    // 输出结果:
    A-> 张三李四 
    C-> 王五 
    

    第四个参数mapSupplier用于返回一个任意类型的Map实例,比如我们希望返回的Map是根据 Key 排序的,可以使用如下写法:

    List<User> userList = Lists.newArrayList(
            new User().setId("B").setName("张三"),
            new User().setId("A").setName("李四"),
            new User().setId("C").setName("王五")
    );
    
    userList.stream().collect(Collectors.toMap(User::getId, User::getName, (n1, n2) -> n1, TreeMap::new));
    
    //输出结果:
    A-> 李四 
    B-> 张三 
    C-> 王五 
    

    或者转化为ConcurrentMap:

    userList.stream().collect(
        Collectors.toMap(User::getId, User::getName, (n1, n2) -> n1, ConcurrentHashMap::new)
    );
    

注意事项

在使用java.util.stream.Collectors 类的 toMap()方法转为 Map集合时,一定要注意当valuenull时会抛 NPE 异常。
java.util.HashMapmerge 方法里会进行如下的判断:

if (value == null || remappingFunction == null)
throw new NullPointerException();

二、总结

在本文中我们快速的学习了Collectors.toMap()方法的使用,它允许我们创建一个新的Map。我们还学习了如果解决Key冲突以及如何返回不同类型的Map还有value为时null的处理方式。

  • 16
    点赞
  • 81
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值