java8 stream to map_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.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 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

建议

优先业务控制,尽量避免 List 中存在 Null

其次推荐第 4 种方法【使用 Optional 对值进行包装】,能很好的避免 NPE 问题

key重复风险

java.lang.IllegalStateException: Duplicate key xx

现象

当 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 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

Collector> 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 the 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 中出现重复

若存在重复场景,则根据实际业务场景选择具体方法【覆盖、拼接、搞成集合】

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值