在使用 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. 若存在重复场景,则根据实际业务场景选择具体方法【覆盖、拼接、搞成集合】

Java8 中使用Stream 让List 转 Map使用总结的更多相关文章

  1. 【Java8新特性】面试官:谈谈Java8中的Stream API有哪些终止操作?

    写在前面 如果你出去面试,面试官问了你关于Java8 Stream API的一些问题,比如:Java8中创建Stream流有哪几种方式?(可以参见:<[Java8新特性]面试官问我:Java8中 ...

  2. Java8中的Stream流式操作 - 入门篇

    作者:汤圆 个人博客:javalover.cc 前言 之前总是朋友朋友的叫,感觉有套近乎的嫌疑,所以后面还是给大家改个称呼吧 因为大家是来看东西的,所以暂且叫做官人吧(灵感来自于民间流传的四大名著之一 ...

  3. 【Java8新特性】面试官问我:Java8中创建Stream流有哪几种方式?

    写在前面 先说点题外话:不少读者工作几年后,仍然在使用Java7之前版本的方法,对于Java8版本的新特性,甚至是Java7的新特性几乎没有接触过.真心想对这些读者说:你真的需要了解下Java8甚至以 ...

  4. java8中的stream().filter()的使用和Optional()

    转: https://www.cnblogs.com/yimiyan/p/5992440.html Optional: https://www.cnblogs.com/zhangboyu/p/7580 ...

  5. Java8中的Stream API

    本篇文章继续介绍Java 8的另一个新特性——Stream API.新增的Stream API与InputStream和OutputStream是完全不同的概念,Stream API是对Java中集合 ...

  6. Java8中那些方便又实用的Map函数

    原创:扣钉日记(微信公众号ID:codelogs),欢迎分享,转载请保留出处. 简介 java8之后,常用的Map接口中添加了一些非常实用的函数,可以大大简化一些特定场景的代码编写,提升代码可读性,一 ...

  7. java8中的stream流遍历

    比较for循环.迭代器.java8Stream流遍历的不同 package cnom.test.testUtils; import java.io.Serializable; import java. ...

  8. java8中的Stream

    Collection.stream() / parallelStream() 1. Stream 1)Filter    stringCollection .stream().filter((s) - ...

  9. 【转】Java8中list转map方法总结

    https://blog.csdn.net/zlj1217/article/details/81611834 背景在最近的工作开发之中,慢慢习惯了很多Java8中的Stream的用法,很方便而且也可以 ...

随机推荐

  1. 第六部分 数据搜索之使用HBASE的API实现条件查询

    题目 使用HADOOP的MAPReduce,实现以下功能: (1)基于大数据计算技术的条件查询:使用mapreduce框架,实现类似Hbase六个字段查询的功能 (2)时段流量统计:以hh:mm:ss ...

  2. 微服务架构开发电商系统需要用Redis、ES和MQ吗?

    如果不用什么很高大上的东西,就是有多个微服务就行这种技术架构会很难吗? 我看了一些视频,他们都用到了es.mq.redis的东西,我想不用这些东西,就简单的有多个服务,这样可行吗? 01 使用微服务你 ...

  3. springboot 项目中css js 等静态资源无法访问的问题

    目录 问题场景 问题分析 问题解决 问题场景 今天在开发一个springboot 项目的时候突然发现 css js 等静态资源竟然都报404找不到,折腾了好久终于把问题都解决了,决定写篇博客,纪录总结 ...

  4. 有趣的css—简单的下雨效果

    简单的下雨效果 前言 最近在b站上看到一个下雨效果的视频,感觉思路很清奇,我也按照自己的思路做了一个简单的下雨效果. 由于我制作GIF图片的工具最多只支持制作33FPS的GIF图,所以看起来可能有一点 ...

  5. buaaoo_fourth_assignment

    你轻轻地走了 一.架构设计 (1)第一次作业 类图 复杂度分析 如上图是本单元第一次作业的架构设计,由于本人最开始未发现可以直接继承官方的类,所以自己将所用到的各种type都重新建了类,于是这就导致了 ...

  6. 排坑&#183;IPhone&IOS中不兼容正则中的断言匹配

    阅文时长 | 1.14分钟 字数统计 | 1834.4字符 主要内容 | 1.问题切入 2.什么是断言匹配 3.断言匹配的替换方案 4.声明与参考资料 『排坑·IPhone&IOS中不兼容正则 ...

  7. jQuery 实现 全选/全不选/反选

    <button id="getall" class="btn btn-warning">全选</button><button id ...

  8. Spring的Xml和JavaConfig 扩展你选哪一个?

    引言 上一篇文章我们有怎么介绍到如何通过XML的形式来定义Spring的扩展<Spring面试高频题如何:自定义XML schema 扩展>,好多人都在吐槽现在都什么年代了,xml还有人再 ...

  9. Linux 系统中如何查看日志 (常用命令) tail -f

    Linux 系统中如何查看日志 (常用命令)  tail -f 日志文件 日 志 文 件 说 明 /var/log/message 系统启动后的信息和错误日志,是Red Hat Linux中最常用的日 ...

  10. k8s运行容器之Job应用(6)

    容器按照持续运行的时间可分为两类:服务类容器和工作类容器. 服务类容器通常持续提供服务,需要一直运行,比如 http server,daemon 等.工作类容器则是一次性任务,比如批处理程序,完成后容 ...