Collectors.toMap保险使用(避坑)
Java8利用Collectors.toMap将List转Map保险使用,避免踩坑!

在实际项目中我们经常会有 List 转 Map 操作,在过去(JAVA8以前)我们可能使用的是 for 循环遍历的方式,这种方式就不做过多赘述。这里主要讲解使用 Collectors.toMap 方式及使用过程需注意的地方,避免踩坑。
先看看 Collectors.toMap 方法,有三个重载方法:
//方法一:2个参数
public static <T, K, U>
Collector<T, ?, Map<K,U>> toMap(Function<? super T, ? extends K> keyMapper,
Function<? super T, ? extends U> valueMapper) {
return toMap(keyMapper, valueMapper, throwingMerger(), HashMap::new);
}
//方法二:3个参数
public static <T, K, U>
Collector<T, ?, Map<K,U>> toMap(Function<? super T, ? extends K> keyMapper,
Function<? super T, ? extends U> valueMapper,
BinaryOperator<U> mergeFunction) {
return toMap(keyMapper, valueMapper, mergeFunction, HashMap::new);
}
//方法三:4个参数
public static <T, K, U, M extends Map<K, U>>
Collector<T, ?, M> toMap(Function<? super T, ? extends K> keyMapper,
Function<? super T, ? extends U> valueMapper,
BinaryOperator<U> mergeFunction,
Supplier<M> mapSupplier) {
BiConsumer<M, T> accumulator
= (map, element) -> map.merge(keyMapper.apply(element),
valueMapper.apply(element), mergeFunction);
return new CollectorImpl<>(mapSupplier, accumulator, mapMerger(mergeFunction), CH_ID);
}
参数含义分别是:
- keyMapper:Key 的映射函数。
- valueMapper:Value 的映射函数。
- mergeFunction:当 Key 冲突时,调用的合并方法。
- mapSupplier:Map 构造器,在需要返回特定的 Map 时使用。
下面分别对三个重载方法进行演示:
目录
方法一:2个参数,keyMapper,valueMapper。
方法二:3个参数,keyMapper,valueMapper,mergeFunction。
方法三:4个参数,keyMapper,valueMapper,mergeFunction,mapSupplier。
总结
方法一:2个参数,keyMapper,valueMapper。
示例:user id没重复的情况下。
public class ToMapTest {
@Data
@Accessors(chain = true)
private static class User {
private String id;
private String name;
}
public static void main(String[] args) {
List<User> list = Arrays.asList(
new User().setId("1").setName("张三"),
new User().setId("2").setName("李四"),
new User().setId("3").setName("王五")
);
//2个参数的toMap方法
Map<String, String> map = list.stream()
.collect(Collectors.toMap(User::getId, User::getName));
System.out.println(map);
}
}
结果:

示例:user id有重复的情况下。
...//其他代码相同
List<User> list = Arrays.asList(
new User().setId("1").setName("张三"),
new User().setId("2").setName("李四"),
//id重复
new User().setId("2").setName("小李四"),
new User().setId("3").setName("王五")
);
...//其他代码相同
结果:直接异常,重复key。
这就是在项目中使用需要注意的地方,当要作为key的值有重复的时候,使用2个参数的toMap方法,会直接抛出异常。
顺便提一下,不要认为这只是个小问题,如果没处理好,在线上出的问题,先不说影响大不大,光说排查修复重新发布所需的时间成本,就已经不少了,这也可能是有的人加班少,有的人要加班多的原因。
再看一下源码:



走到这里,就可以看到,如果oldValue有值,那么就会执行抛异常的方法。
那么,如何避免这种情况呢?JAVA早有考虑:可以使用3个参数的toMap方法。
方法二:3个参数,keyMapper,valueMapper,mergeFunction。
//3个参数的toMap方法
Map<String, String> map = list.stream().collect(
Collectors.toMap(
User::getId,
User::getName,
//v1、v2表示两个重复key的value,下面是对value的处理,可根据自己业务需求来
(v1, v2) -> v1 + "、" + v2
)
);
结果:可以看到没有出现异常,结果对value进行了处理。

这里还需要注意一点:
HashMap对象的key、value值均可为null。
但是,toMap的话,value为null,也是会抛异常-空指针异常!
看下示例:
List<User> list = Arrays.asList(
new User().setId("1").setName("张三"),
new User().setId("2").setName("李四"),
new User().setId("3").setName("王五"),
//没有设置name,name为null。
new User().setId("9")
);
结果: 
看下源码:
还是调用map的merge方法这里:
不言而喻了。

如果value可能出现null,则需要在toMap()的第二个参数进行空(null)值的判断逻辑;例如:
Map<String, String> map = list.stream().collect(
Collectors.toMap(
User::getId,
//value的空值判断
u->u.getName() == null? "未知" : u.getName(),
(v1, v2) -> v1 + "、" + v2
)
);
最后,再来看看4个参数的。
方法三:4个参数,keyMapper,valueMapper,mergeFunction,mapSupplier。
第四个参数(mapSupplier)用于自定义返回 Map 类型,比如我们希望返回的 Map 是根据 Key 排序:
//4个参数的toMap方法
Map<String, String> map = list.stream().collect(
Collectors.toMap(
User::getId,
u->u.getName() == null? "未知" : u.getName(),
(v1, v2) -> v1 + "、" + v2,
TreeMap::new
)
);
结果:

总结
Collectors.toMap 确实带来方便,但是,与此同时,也需要注意两点(你也可以认为是2个坑):
- 需考虑是否有key重复情况;
- 需考虑是否有value为null情况。
以上两点在阿里的Java开发手册中也明确指出:


更多推荐



所有评论(0)