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. 若存在重复场景,则根据实际业务场景选择具体方法【覆盖、拼接、搞成集合】
  • 相关阅读:
    开始学习编写用于 Windows SideShow 设备的小工具【转】
    Windows Mobile 6.5 Developer Tool Kit 下载
    Microsoft Security Essentials 微软免费杀毒软件下载
    SQL Server 2008 空间数据存储摘抄(SRID 点 MultiPoint LineString MultiLineString 多边形 MultiPolygon GeometryCollection)
    Vista Sidebar Gadget (侧边栏小工具)开发教程 (2)
    Vista Sidebar Gadget (侧边栏小工具)开发教程 (4)
    负载测试、压力测试和性能测试的异同
    Windows Server 2008 Vista Sidebar Gadget (侧边栏小工具) 入门开发实例
    Silverlight Tools 安装失败 解决办法
    SQL Server 2008 空间数据库 空间索引概念及创建(取自帮助)
  • 原文地址:https://www.cnblogs.com/asimov/p/13960844.html
Copyright © 2011-2022 走看看