在使用 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. iwrite复制攻略

    打开iwrite,一提交作业,发现: 这可咋办啊! 那就跟着步骤来呗: 按F12打开元素审查 点一下左上角 再点一下文本框,就能定位到HTML中的位置 在文本框中写几个字母,康康具体位置: 那就复制进 ...

  2. 【python】Leetcode每日一题-丑数

    [python]Leetcode每日一题-丑数 [题目描述] 给你一个整数 n ,请你判断 n 是否为 丑数 .如果是,返回 true :否则,返回 false . 丑数 就是只包含质因数 2.3 和 ...

  3. Rabbit MQ一些参数解释

    //ConnectionFactory(连接工厂): 生产Connection的的工厂 //Connection(连接):是RabbitMQ的socket的长链接,它封装了socket协议相关部分逻辑 ...

  4. ES 6 中的箭头函数及用法

    ES6标准新增了一种新的函数:Arrow Function(箭头函数). 主要的几种写法如下: 组成: 参数 => 语句, 参数不是1个: (参数,参数2)=>语句 语句不止一条: 参数 ...

  5. 不融资、不上市、不快马圈地…“佛系”ZOHO的中国生意经

    来源:钛媒体 作者:秦聪慧 "技术比肩SAP.直追微软的这家25岁"非典型"国际大厂会继续佛系下去吗? ZOHO研发中心大楼 在中国,有家相对低调的"舶来&qu ...

  6. 匿名函数lambda / map()方法

    lambda一般配合其他方法使用,一般使用在只用过一次就不用的函数,那就没必要特意去定义 lambda能支持的最复杂的语句就是三元运算 例如: lambda x,y: x*y if x < y ...

  7. 使用gulp搭建项目

    项目源码地址 前期准备工作 首先确保本机安装了 node gulp中文文档 安装 gulp 命令行工具 npm install --global gulp-cli 在项目目录下创建 package.j ...

  8. [刷题] 1022 D进制的A+B (20分)

    思路 设t = A + B,将每一次t % d的结果保存在int类型的数组s中 然后将t / d,直到 t 等于 0为止 此时s中保存的就是 t 在 D 进制下每一位的结果的倒序 最后倒序输出s数组 ...

  9. [bug] MySQL 无法删除表

    参考 https://blog.csdn.net/smbluesky/article/details/82427121

  10. QT相关书籍

    文章转载自:http://www.cctry.com/thread-290005-1-1.html 最近一段时间,陆陆续续给大家更新了不少基于Qt开发的不错的书籍,可以说每本都不错.不过放在这一堆大家 ...