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使用的资料请关注我们其它相关文章!

(0)

相关推荐

  • java8快速实现List转map 、分组、过滤等操作

    利用java8新特性,可以用简洁高效的代码来实现一些数据处理. 定义1个Apple对象: public class Apple { private Integer id; private String name; private BigDecimal money; private Integer num; public Apple(Integer id, String name, BigDecimal money, Integer num) { this.id = id; this.name =

  • Java8 将一个List<T>转为Map<String,T>的操作

    将 List 转为 Map<String, T> public class AnswerApp { public static void main(String[] args) throws Exception { List<String> names = Lists.newArrayList("Answer", "AnswerAIL", "AI"); Map<String, Integer> map = na

  • Java8 实现stream将对象集合list中抽取属性集合转化为map或list

    首先新建一个实体类Person @Data public class Person { /** 编码 */ private String code; /** 名字 */ private String name; public Person(String code, String name) { this.code = code; this.name = name; } } 实例化三个对象放入list集合中 public static void main(String[] args) { Pers

  • java8 Stream list to Map key 重复 value合并到Collectio的操作

    java8 Stream list to Map key 重复 value合并到Collectio 关于把list转换成key value的map有很多博客上都有实现,这里是一个把value放入到集合中去 List<String> list = Lists.newArrayList("1", "2", "3", "1"); Map<String, List<String>> map = li

  • 详解Java8新特性Stream之list转map及问题解决

    List集合转Map,用到的是Stream中Collectors的toMap方法:Collectors.toMap 具体用法实例如下: //声明一个List集合 List<Person> list = new ArrayList(); list.add(new Person("1001", "小A")); list.add(new Person("1002", "小B")); list.add(new Person

  • Java8中forEach语句循环一个List和Map

    在这篇文章中,我将向您展示如何用新的Java 8 forEach语句循环一个List和Map. 1.forEach 和 Map 1.1.常规循环Map常用的方法. Map<String ,Integer> items = new HashMap<>(); items.put("A",10); items.put("B",20); items.put("C",30); items.put("D",40);

  • 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<>(); S

  • 详解java8中的Stream数据流

    Stream是java8引入的一个重度使用lambda表达式的API.Stream使用一种类似用SQL语句从数据库查询数据的直观方式来提供一种对Java集合运算和表达的高阶抽象.直观意味着开发者在写代码时只需关注他们想要的结果是什么而无需关注实现结果的具体方式.这一章节中,我们将介绍为什么我们需要一种新的数据处理API.Collection和Stream的不同之处以及如何将StreamAPI应用到我们的编码中. 筛选重复的元素 Stream 接口支持 distinct 的方法, 它会返回一个元素

  • 初识Java8中的Stream

    lambda表达式是stream的基础,初学者建议先学习lambda表达式,http://www.jb51.net/article/121129.htm 1.初识stream 先来一个总纲: 东西就是这么多啦,stream是java8中加入的一个非常实用的功能,最初看时以为是io中的流(其实一点关系都没有),让我们先来看一个小例子感受一下: @Before public void init() { random = new Random(); stuList = new ArrayList<St

  • Java8中的Stream 流实践操作

    目录 1 前言 2 Stream 的分类 3 Stream 的操作 3.1 创建流的方式 3.2 流的中间操作 3.3 流的终止操作 总结 1 前言 Stream 是 java8 中处理集合的抽象概念,可以执行非常复杂的查询.过滤和映射数据等操作.Stream API 提供了一种高效的处理数据方式,Stream 对集合数据的操作可以说是非常的方便.Stream 是流,不是一种数据结构,也不会保存数据,只是一种数据处理方式,从一种数据组织结构到另外一种数据结构. 2 Stream 的分类 按照 S

  • 总结一下关于在Java8中使用stream流踩过的一些坑

    Java8的stream流 第一个坑: Collectors.toAsList()其实是new了一个list,在向里面赋值. 注意这里Collectors.toList()的写法,这里其实是底层new ArraryList().筛选的数据放到一个新的list.虽然标1处和标2处是同一个变量,但是内存地址是不一样啊.下面的逻辑时把hldrPolVOList中的某些元素删除.但是这个方法执行完后其实是没有删除里面元素的.原因就是这里的new ArraryList()更改了内存地址造成的. 测试: 解

  • Java8中的Stream流式操作教程之王者归来

    前言 相对于Java8之前的Java的相关操作简直是天差地别,Java8 的流式操作的出现,也很大程度上改变了开发者对于Java的繁琐的操作的印象,从此,Java也走向了函数式编程的道路! 1 流的创建 1.1 流的创建方法 既然需要聊聊流的操作,那么,首先还是先看看怎么创建流. 创建流的方法有三种,分别是:Stream.of().Stream.iterate().Stream.generate(),然后,分别看一下这三个方法的声明. static <T> Stream<T> of

  • Java8中利用stream对map集合进行过滤的方法

    前言 Stream 是用函数式编程方式在集合类上进行复杂操作的工具,其集成了Java 8中的众多新特性之一的聚合操作,开发者可以更容易地使用Lambda表达式,并且更方便地实现对集合的查找.遍历.过滤以及常见计算等. 最近公司在大张旗鼓的进行代码审核,从中也发现自己写代码的不好习惯.一次无意的点到了公司封装的对map集合过滤的方法,发现了stream.于是研究了一下.并对原有的代码再次结合Optional进行重构下 原有方法说明 主要处理过滤条件Map对象,过滤掉了null和空字符串 等操作 这

  • Java8中Stream的使用方式

    目录 前言: 1. 为什么有经验的老手更倾向于使用Stream 2. Stream 的使用方式 3. Stream 的创建 4. Stream 中间操作 5. Stream 终止操作 6. Stream 特性 前言: 相信有很多刚刚入坑程序员的小伙伴被一些代码搞的很头疼,这些代码让我们既感觉到很熟悉,又很陌生的感觉.我们很多刚入行的朋友更习惯于使用for循环或是迭代器去解决一些遍历的问题,但公司里很多老油子喜欢使用Java8新特性Stream流去做,这样可以用更短的代码实现需求,但是对于不熟悉的

  • java8中Stream的使用示例教程

    前言 Java8中提供了Stream对集合操作作出了极大的简化,学习了Stream之后,我们以后不用使用for循环就能对集合作出很好的操作. 本文将给大家详细介绍关于java8 Stream使用的相关内容,下面话不多说了,来一起看看详细的介绍吧 1. 原理 Stream 不是集合元素,它不是数据结构并不保存数据,它是有关算法和计算的,它更像一个高级版本的 Iterator. 原始版本的 Iterator,用户只能显式地一个一个遍历元素并对其执行某些操作: 高级版本的 Stream,用户只要给出需

随机推荐