zoukankan      html  css  js  c++  java
  • Java8 中使用Stream 让List 转 Map使用总结

    在使用 Java 的新特性 Collectors.toMap() 将 List 转换为 Map 时存在一些不容易发现的问题,这里总结一下备查。

    空指针风险

    java.lang.NullPointerException

    当 List 中有 null 值的时候,使用 Collectors.toMap() 转为 Map 时,会报 java.lang.NullPointerException,如下:

    List<SdsTest> sdsTests = new ArrayList<>();
        SdsTest sds1 = new SdsTest("aaa","aaa");
        SdsTest sds2 = new SdsTest("bbb",null);
    
        sdsTests.add(sds1);
        sdsTests.add(sds2);
    
        Map<String, String> map = sdsTests.stream().collect(Collectors.toMap(SdsTest::getName, SdsTest::getAge));    
        System.out.println(map.toString());
    
    ---------
    运行错误:
    Exception in thread "main" java.lang.NullPointerException
        at java.util.HashMap.merge(HashMap.java:1216)
        at java.util.stream.Collectors.lambda$toMap$150(Collectors.java:1320)
        .....

      原因是 toMap() 方法中使用 Map.merge() 方法合并时,merge 不允许 value 为 null 导致的,源码如下:

    default V merge(K key, V value, BiFunction<? super V, ? super V, ? extends V> remappingFunction) {
        Objects.requireNonNull(remappingFunction);
        // 在这里判断了value不可为null
        Objects.requireNonNull(value);
        V oldValue = get(key);
        V newValue = (oldValue == null) ? value : remappingFunction.apply(oldValue, value);
        ...

    解决方法

    1. 业务控制不要出现 Null 值【有 Null 的地方,可以赋值默认值】
    2. 在转换时加判断,如果为 null,则给一个默认值
    Map<String, String> map = sdsTests.stream().collect(Collectors.toMap(SdsTest::getName, sdsTest -> sdsTest.getAge() == null ? "0" : sdsTest.getAge()));
    1. 使用 collect(..) 构建,允许空值
    Map<String, String> nmap = sdsTests.stream().collect(HashMap::new,(k, v) -> k.put(v.getName(), v.getAge()), HashMap::putAll);
    // TODO 下游业务从Map取值要做NPE判断
    1. 使用 Optional 对值进行包装
    Map<String, Optional<String>> opmap = sdsTests.stream().collect(Collectors.toMap(SdsTest::getName, sdsTest -> Optional.ofNullable(sdsTest.getAge())));
    System.out.println("bbb.age=" + opmap.get("bbb").orElse("0"));
    ------------
    输出:
    bbb.age=0

    建议

    1. 优先业务控制,尽量避免 List 中存在 Null
    2. 其次推荐第 4 种方法【使用 Optional 对值进行包装】,能很好的避免 NPE 问题

    key重复风险

    java.lang.IllegalStateException: Duplicate key xx

    当 List 中有重复值的时候,使用 Collectors.toMap() 转为 Map 时,会报:java.lang.IllegalStateException: Duplicate key xx,例如

    List<SdsTest> sdsTests = new ArrayList<>();
        SdsTest sds1 = new SdsTest("aaa","aaa");
        SdsTest sds2 = new SdsTest("aaa","ccc");
    
        sdsTests.add(sds1);
        sdsTests.add(sds2);
    
        Map<String, String> map = sdsTests.stream().collect(Collectors.toMap(SdsTest::getName, SdsTest::getAge));    
        System.out.println(map.toString());
    
    ---------
    运行错误:
    Exception in thread "main" java.lang.IllegalStateException: Duplicate key aaa
            at java.util.stream.Collectors.lambda$throwingMerger$92(Collectors.java:133)
            at java.util.stream.Collectors$$Lambda$6/1177096266.apply(Unknown Source)
            at java.util.HashMap.merge(HashMap.java:1245)
                .....

    原因是两个参数的toMap(xx, xx)方法, 当出现重复key触发merge时,直接抛出异常。源码如下:

    public static <T, K, U>
    Collector<T, ?, Map<K,U>> toMap(Function<? super T, ? extends K> keyMapper,
                                Function<? super T, ? extends U> valueMapper) {
         // 注意这里的throwingMerger()
         return toMap(keyMapper, valueMapper, throwingMerger(), HashMap::new);
    }

    接下来我们看throwingMerger() 方法:【注意方法注释

    /**
     * Returns a merge function, suitable for use in
     * {@link Map#merge(Object, Object, BiFunction) Map.merge()} or
     * {@link #toMap(Function, Function, BinaryOperator) toMap()}, which always
     * throws {@code IllegalStateException}.  This can be used to enforce the
     * assumption that the elements being collected are distinct.
     *
     * @param <T> the type of input arguments to the merge function
     * @return a merge function which always throw {@code IllegalStateException}
     */
     private static <T> BinaryOperator<T> throwingMerger() {
          return (u,v) -> { throw new IllegalStateException(String.format("Duplicate key %s", u)); };
     }
    ...

    解决方法

    1. 业务控制尽量不要出现重复值
    2. 出现重复 key 时,使用后面的 value 覆盖前面的 value
    SdsTest sds1 = new SdsTest("aaa","aaa");
    SdsTest sds2 = new SdsTest("bbb","bbb");
    SdsTest sds3 = new SdsTest("aaa","ccc");
    
    sdsTests.add(sds1);
    sdsTests.add(sds2);
    sdsTests.add(sds3);
    
    // 写法一
    Map<String, String> nmap = sdsTests.stream().collect(HashMap::new,(k, v) -> k.put(v.getName(), v.getAge()), HashMap::putAll);
    System.out.println("nmap->:" + nmap.toString());
    
    // 写法二
    Map<String, String> nmap1 = sdsTests.stream().collect(Collectors.toMap(SdsTest::getName, SdsTest::getAge, (k1, k2) -> k2));
    System.out.println("nmap1->:" + nmap1.toString());
    ...
    ----------------------
    输出:
    nmap->:{aaa=ccc, bbb=bbb}
    nmap1->:{aaa=ccc, bbb=bbb}
    1. 出现重复 key 时,把对应的 value 拼接起来
    ...
    Map<String, String> nmap1 = sdsTests.stream().collect(Collectors.toMap(SdsTest::getName, SdsTest::getAge, (k1, k2) -> k1 + "," + k2));
    System.out.println("nmap1->:" + nmap1.toString());
    ...
    ----------------
    输出:
    nmap1->:{aaa=aaa,ccc, bbb=bbb}
    1. 把重复 key 的值拼成一个集合
    ......
    Map<String, List<String>> map = sdsTests.stream().collect(Collectors.toMap(SdsTest::getName,
        s -> {
            List<String> ages = new ArrayList<>();
            ages.add(s.getAge());
            return ages;
        },
        (List<String> v1, List<String> v2) -> {
            v1.addAll(v2);
            return v1;
        }));
    System.out.println("map->"+map.toString());
    ------------
    输出:
    map->{aaa=[aaa, ccc], bbb=[bbb]}

    建议:

    1. 优先业务控制,尽量避免 List 中出现重复
    2. 若存在重复场景,则根据实际业务场景选择具体方法【覆盖、拼接、搞成集合】
  • 相关阅读:
    解决小问题VC2008通过ADO连接ACCESS (转)
    给zblog站点备份和搬家
    vc中自定义编译时的输出消息 .
    更改MFC 对话框控件的tab顺序
    dotnetbar 添加到工具栏方法
    关于完成端口IOCP异步接收连接函数AcceptEx注意事项 (转)
    中英文url解码vc++源程序
    c#关于网页内容抓取,简单爬虫的实现。(包括动态,静态的)
    图解使用Broadcom Advanced Control Suite 2设置Broadcom单网卡跨多VLAN(转)
    windbg调试环境变量记录
  • 原文地址:https://www.cnblogs.com/Kevin-ZhangCG/p/14852528.html
Copyright © 2011-2022 走看看