使用Java的stream中的Collectors可以很方便地做容器间的转换,可以少写很多代码。但是其中有暗含的坑需要注意和避免,本文探讨Collectors.toMap(JDK8版本)。

Collectors.toMap可以将一个流转化成Map,常见于需要将List转换成Map以便于进一步操作的场景,比如在通过RPC接口获取一个返回结果、从DB中查询到匹配的多条数据后,对其按某个字段(经常是主键id)做分组。这里先定义一个简单的类:

public class User {
private Long id; private String name;
}

后续所有代码的目的均为将这个User类组成的List转换为id作为key、name作为value的Map。

1. 不使用stream

不使用stream时,需要先new一个map,然后手动把list的每一项放入map

    public void test0() {
User user1 = new User();
user1.setId(1L);
user1.setName("1"); User user2 = new User();
user2.setId(2L);
user2.setName("2"); List<User> list = Lists.newArrayList();
list.add(user1);
list.add(user2);
Map<Long, String> map = new HashMap<>();
for(User user : list) {
map.put(user.getId(), user.getName());
}
}

2. 使用stream的Collectors.toMap

构造list的代码不变,转化map的代码可以简化为

Map<Long, String> map = list.stream().collect(Collectors.toMap(User::getId, User::getName));

相较第一种,简洁多了。

但是,如果User1和User2的id是一样的,会发生什么情况?将代码中user2.setId(2L)修改为user2.setId(1L),再次执行,发现抛异常java.lang.IllegalStateException: Duplicate key 1,说明在merge时报错了:key不允许重复。

但是有些情况下key确实是可以重复的,比如我调用上游的数据,上游没做校验和控制;再或者这个字段本身不是惟一的,多个数据可能重复。那么如何改进呢?

3. Collectors.toMap指定merge函数

可以自定义一个merge函数来确定key重复时,如何取value。比如下面这种写法,是保留第一个value。你也可以保留第二个,或者是做一些更复杂的处理。

Map<Long, String> map = list.stream().collect(Collectors.toMap(User::getId, User::getName, (x1,x2)->x1));

4. value为null的场景

传入merge方法以后,看似万事大吉了,没想到还有坑。将user2.setName(null),会发现抛了NullPointerException异常,异常栈信息为:



可以看到对应的源码里,value是不允许为null的。

虽然说Map的value是支持null值的,但是map自己的merge方法天生不支持,此时仅靠自定义merge方法也已经无能为力了。如果仍然想使用Collectors.toMap,需要手动处理null的值,比如:

Map<Long, String> map = list.stream().collect(Collectors.toMap(User::getId, value -> Optional.ofNullable(value.getName()).orElse("")));

当然,这样处理后的map的value并不是实际的值,并不适用于所有场景。

这样看来,Collectors.toMap的局限性无法避免了,使用的时候要确认不会发生预期的问题。

当然你也可以做一些预处理,比如使用filter过滤掉value=null的数据,来规避这个问题。

5. key=null时会怎么样?

不会怎么样,一切正常。

public void test5() {
User user1 = new User();
user1.setId(null);
user1.setName("1"); User user2 = new User();
user2.setId(2L);
user2.setName("2"); List<User> list = Lists.newArrayList();
list.add(user1);
list.add(user2);
Map<Long, String> map = list.stream().collect(Collectors.toMap(User::getId, User::getName));
System.out.print(map);
}

6. 还有什么要注意的?

上述的例子,均是建立在list不为空的前提下进行的。如果list本身为null,在调用stream()时自然也会抛NullPointerException;如果list是空的(内容为空但是容器本身初始化过,如list = new ArrayList<>()),则不会报错。

小结

  • 调用stream()前先确定容器本身是否为null
  • 如果不确定要通过Collectors.toMap转换为map的源容器的数据
    • 对应key是否会重复,可以在toMap()传入merge方法
    • 对应value是否为null,可以做过滤或指定默认值
  • 如果不想思考和求证,还是继续用for循环吧

Collectors.toMap的暗坑与避免方式的更多相关文章

  1. Java8 Collectors.toMap的坑

    按照常规思维,往一个map里put一个已经存在的key,会把原有的key对应的value值覆盖,然而通过一次线上问题,发现Java8中的Collectors.toMap反其道而行之,它默认给抛异常,抛 ...

  2. 使用Collectors.toMap遇到NullPointerException

    这个坑也是踩过好几次了,记录一笔. 当试图使用Collectors.toMap将一个stream收集为Map的时候,若构造map的valueMapper返回null时,则会报NullPointerEx ...

  3. HTTP 规范中的那些暗坑

    HTTP 协议可以说是开发者最熟悉的一个网络协议,「简单易懂」和「易于扩展」两个特点让它成为应用最广泛的应用层协议. 虽然有诸多的优点,但是在协议定义时因为诸多的博弈和限制,还是隐藏了不少暗坑,让人一 ...

  4. java 8 lamda Stream的Collectors.toMap 参数

    使用toMap()函数之后,返回的就是一个Map了,自然会需要key和value.toMap()的第一个参数就是用来生成key值的,第二个参数就是用来生成value值的.第三个参数用在key值冲突的情 ...

  5. Collectors.toMap不允许Null Value导致NPE

    背景 线上某任务出现报警,报错日志如下: java.lang.NullPointerException: null at java.util.HashMap.merge(HashMap.java:12 ...

  6. java8 list转Map报错Collectors.toMap :: results in "Non-static method cannot be refernced from static context"

    1.问题:java8 list转Map 报错Collectors.toMap :: results in "Non-static method cannot be refernced fro ...

  7. SpringBoot填坑系列---XML方式配置数据库

    本次只是简单的运用SpringBoot搭建框架,对其原理并不做深入的探究 1.POM文件 <?xml version="1.0" encoding="UTF-8&q ...

  8. python pyinstaller打包exe暗坑1

    环境 python2.7.9 win-xp 今天打包了一个小脚本,结果打开报错

  9. jenkins构建基于gradle的springboot项目CI采坑(采用jar方式部署)

    试了一堆插件,最后用的还是 publish over SSH jenkins基本配置不多说了,就是配置一下git仓储,配置一下gradle执行命令 clean bootRepackage 之后执行Se ...

  10. [转]深入理解Java 8 Lambda(类库篇——Streams API,Collectors和并行)

    以下内容转自: 作者:Lucida 微博:@peng_gong 豆瓣:@figure9 原文链接:http://zh.lucida.me/blog/java-8-lambdas-insideout-l ...

随机推荐

  1. 用ArcGIS模型构建器生成、导出Python转换空间坐标系的代码

      本文介绍在ArcMap软件中,通过创建模型构建器(ModelBuilder),导出地理坐标系与投影坐标系之间相互转换的Python代码的方法.   在GIS领域中,矢量.栅格图层的投影转换是一个经 ...

  2. 叮~OpenSCA社区拍了拍您并发来一份开源盛会邀请函

    2023数字供应链安全大会(DSS 2023)将于8月10日在北京·国家会议中心举办.本次大会以"开源的力量"为主题,由悬镜安全主办,ISC互联网安全大会组委会.中国软件评测中心( ...

  3. IDEA插件Material Theme UI 激活

    介绍 "Material Theme UI" 是一款为 IntelliJ IDEA 提供现代化材料设计主题的插件,通过重新设计IDE的外观,为开发人员带来更加美观.富有活力的用户体 ...

  4. Angular系列教程之观察者模式和RxJS

    .markdown-body { line-height: 1.75; font-weight: 400; font-size: 16px; overflow-x: hidden; color: rg ...

  5. 如果诸葛亮会编程,用Java写出师表...

    继上一篇 "如果诸葛亮用C#写出师表..."后,站长想自己的第一语言是Java,虽然平时工作上用的不多,也用Java实现一遍吧,改改就是了,无非就是: C#的Console.Wri ...

  6. JAVA - 文件切分存储 - FileInputStream 流形式

    package stream; import java.io.*; /** * @author*/ public class TestStream { public static void main( ...

  7. [转帖]配置 Windows XP 正常上网(TLS HTTPS),连接到 NAS

    https://zhuanlan.zhihu.com/p/208685816# 学习一下. 知乎用户8kqKq9 等 45 人赞同了该文章 Windows XP 是经典的.高效的.可靠的.性能良好的操 ...

  8. [转帖]SQL SERVER DBCC命令详解

    https://developer.aliyun.com/article/867768   简介: SQL数据库开发 DBCC DROPCLEANBUFFERS:从缓冲池中删除所有缓存,清除缓冲区 在 ...

  9. [转帖]centos6 free 和 centos 7的free 的差异与对比

    目录 一 centos6 free 常用参数和含义 centos6 free 命令示例 free 值讲解 计算公式 二 centos7 free 常用的参数 centos7 free 命令示例 计算公 ...

  10. [转帖]TiDB的tikv节点的压缩算法

    简介:TiDB的tikv节点实用的RocksDB,RocksDB的默认压缩算法为:[no:no:lz4:lz4:lz4:zstd:zstd] RocksDB 每一层数据的压缩方式,可选的值为:no,s ...