Java8|Java8 中使用Stream 让List 转 Map使用问题小结
在使用 Java 的新特性 Collectors.toMap() 将 List 转换为 Map 时存在一些不容易发现的问题,这里总结一下备查。
空指针风险
java.lang.NullPointerException
当 List 中有 null 值的时候,使用 Collectors.toMap() 转为 Map 时,会报 java.lang.NullPointerException,如下:
List sdsTests = new ArrayList<>(); SdsTest sds1 = new SdsTest("aaa","aaa"); SdsTest sds2 = new SdsTest("bbb",null); sdsTests.add(sds1); sdsTests.add(sds2); Map map = sdsTests.stream().collect(Collectors.toMap(SdsTest::getName, SdsTest::getAge)); System.out.println(map.toString()); ---------运行错误:Exception in thread "main" java.lang.NullPointerExceptionat 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不可为nullObjects.requireNonNull(value); V oldValue = https://www.it610.com/article/get(key); V newValue = (oldValue == null) ? value : remappingFunction.apply(oldValue, value); ...
解决方法
业务控制不要出现 Null 值【有 Null 的地方,可以赋值默认值】在转换时加判断,如果为 null,则给一个默认值
Map map = sdsTests.stream().collect(Collectors.toMap(SdsTest::getName, sdsTest -> sdsTest.getAge() == null ? "0" : sdsTest.getAge()));
使用 collect(..) 构建,允许空值
Map nmap = sdsTests.stream().collect(HashMap::new,(k, v) -> k.put(v.getName(), v.getAge()), HashMap::putAll); // TODO 下游业务从Map取值要做NPE判断
使用 Optional 对值进行包装
Map> 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
【Java8|Java8 中使用Stream 让List 转 Map使用问题小结】建议
- 优先业务控制,尽量避免 List 中存在 Null
- 其次推荐第 4 种方法【使用 Optional 对值进行包装】,能很好的避免 NPE 问题
当 List 中有重复值的时候,使用 Collectors.toMap() 转为 Map 时,会报:java.lang.IllegalStateException: Duplicate key xx,例如
List sdsTests = new ArrayList<>(); SdsTest sds1 = new SdsTest("aaa","aaa"); SdsTest sds2 = new SdsTest("aaa","ccc"); sdsTests.add(sds1); sdsTests.add(sds2); Map map = sdsTests.stream().collect(Collectors.toMap(SdsTest::getName, SdsTest::getAge)); System.out.println(map.toString()); ---------运行错误:Exception in thread "main" java.lang.IllegalStateException: Duplicate key aaaat 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 staticCollector > 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. * * @paramthe type of input arguments to the merge function * @return a merge function which always throw {@code IllegalStateException} */ private static BinaryOperator 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 nmap = sdsTests.stream().collect(HashMap::new,(k, v) -> k.put(v.getName(), v.getAge()), HashMap::putAll); System.out.println("nmap->:" + nmap.toString()); // 写法二Map 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 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> map = sdsTests.stream().collect(Collectors.toMap(SdsTest::getName,s -> {List ages = new ArrayList<>(); ages.add(s.getAge()); return ages; },(List v1, List v2) -> {v1.addAll(v2); return v1; })); System.out.println("map->"+map.toString()); ------------输出:map->{aaa=\[aaa, ccc\], bbb=\[bbb\]}
建议:
- 优先业务控制,尽量避免 List 中出现重复
- 若存在重复场景,则根据实际业务场景选择具体方法【覆盖、拼接、搞成集合】
推荐阅读
- 热闹中的孤独
- Shell-Bash变量与运算符
- JS中的各种宽高度定义及其应用
- 2021-02-17|2021-02-17 小儿按摩膻中穴-舒缓咳嗽
- 深入理解Go之generate
- 由浅入深理解AOP
- 异地恋中,逐渐适应一个人到底意味着什么()
- 【译】20个更有效地使用谷歌搜索的技巧
- 我眼中的佛系经纪人
- 《魔法科高中的劣等生》第26卷(Invasion篇)发售