Java8 中使用Stream 让List 转 Map使用问题小结

 更新时间:2021年06月16日 14:59:32   作者:Kevin.ZhangCG  
这篇文章主要介绍了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);
    ...

解决方法

业务控制不要出现 Null 值【有 Null 的地方,可以赋值默认值】在转换时加判断,如果为 null,则给一个默认值

Map<String, String> map = sdsTests.stream().collect(Collectors.toMap(SdsTest::getName, sdsTest -> sdsTest.getAge() == null ? "0" : sdsTest.getAge()));

使用 collect(..) 构建,允许空值

Map<String, String> nmap = sdsTests.stream().collect(HashMap::new,(k, v) -> k.put(v.getName(), v.getAge()), HashMap::putAll);
// TODO 下游业务从Map取值要做NPE判断

使用 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

建议

  • 优先业务控制,尽量避免 List 中存在 Null
  • 其次推荐第 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)); };
 }
...

解决方法

  • 业务控制尽量不要出现重复值
  • 出现重复 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}

出现重复 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}

把重复 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\]}

建议:

  • 优先业务控制,尽量避免 List 中出现重复
  • 若存在重复场景,则根据实际业务场景选择具体方法【覆盖、拼接、搞成集合】

以上就是Java8 中使用Stream 让List 转 Map使用总结的详细内容,更多关于Java8 List 转 Map使用的资料请关注脚本之家其它相关文章!

相关文章

  • 详解微信小程序 同步异步解决办法

    详解微信小程序 同步异步解决办法

    这篇文章主要介绍了详解微信小程序 同步异步解决办法的相关资料,需要的朋友可以参考下
    2017-06-06
  • Java @SpringBootApplication注解深入解析

    Java @SpringBootApplication注解深入解析

    这篇文章主要给大家介绍了关于Java @SpringBootApplication注解的相关资料,@SpringBootApplication这个注解是Spring Boot项目的基石,创建SpringBoot项目之后会默认在主类加上,文中通过代码介绍的非常详细,需要的朋友可以参考下
    2024-02-02
  • SpringBoot项目中引入本地JAR包配置的几种方法

    SpringBoot项目中引入本地JAR包配置的几种方法

    SpringBoot有时需要引入本地JAR包以便重用已有的代码库或者第三方库,本文主要介绍了SpringBoot项目中引入本地JAR包配置的几种方法,具有一定的参考价值,感兴趣的可以了解一下
    2024-08-08
  • 浅析idea生成war包放入tomcat的路径访问问题

    浅析idea生成war包放入tomcat的路径访问问题

    这篇文章主要介绍了关于idea 生成war 包放入tomcat的路径访问问题,本文通过图文并茂的形式给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2020-04-04
  • MyBatis如何实现流式查询的示例代码

    MyBatis如何实现流式查询的示例代码

    这篇文章主要介绍了MyBatis 如何实现流式查询的示例代码,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-04-04
  • 确保SpringBoot定时任务只执行一次的常见方法小结

    确保SpringBoot定时任务只执行一次的常见方法小结

    在Spring Boot项目中,确保定时任务只执行一次是一个常见的需求,这种需求可以通过多种方式来实现,以下是一些常见的方法,它们各具特点,可以根据项目的实际需求来选择最合适的方法,需要的朋友可以参考下
    2024-10-10
  • springboot2.x集成swagger的方法示例

    springboot2.x集成swagger的方法示例

    这篇文章主要介绍了springboot2.x集成swagger的方法示例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2019-05-05
  • C++享元模式详解

    C++享元模式详解

    这篇文章主要为大家详细介绍了C++设计模式之享元模式Flyweight,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2021-09-09
  • Redis介绍和使用场景详解

    Redis介绍和使用场景详解

    这篇文章主要为大家详细介绍了Redis介绍和使用场景,需要的朋友可以参考,具体内容如下
    2018-04-04
  • RocketMQ实现消息分发的步骤

    RocketMQ实现消息分发的步骤

    RocketMQ 实现消息分发的核心机制是通过 Topic、Queue 和 Consumer Group 的配合实现的,下面给大家介绍RocketMQ实现消息分发的步骤,感兴趣的朋友一起看看吧
    2024-03-03

最新评论