Ref
for循环完成List->Map
我们经常会用到 List 转 Map 操作,在过去我们可能使用的是 for 循环遍历的方式,如下所示。
// 简单对象
@Accessors(chain = true) // 链式方法
@lombok.Data
class User {
private String id;
private String name;
}
List<User> userList = Lists.newArrayList(
new User().setId("A").setName("张三"),
new User().setId("B").setName("李四"),
new User().setId("C").setName("王五")
);
希望转成 Map 的格式为
A-> 张三
B-> 李四
C-> 王五
过去的做法(循环)
Map<String, String> map = new HashMap<>();
for (User user : userList) {
map.put(user.getId(), user.getName());
}
使用 Java8 特性
Java8 中新增了 Stream 特性,使得我们在处理集合操作时更方便了。
以上述例子为例,我们可以一句话搞定
userList.stream().collect(Collectors.toMap(User::getId, User::getName));
当然,如果希望得到 Map 的 value 为对象本身时,可以这样写
userList.stream().collect(Collectors.toMap(User::getId, t -> t));
//或
userList.stream().collect(Collectors.toMap(User::getId, Function.identity()));
Collectors.toMap 方法的参数
Collectors.toMap 有三个重载方法
// 1
toMap(Function<? super T, ? extends K> keyMapper, Function<? super T, ? extends U> valueMapper);
// 2
toMap(Function<? super T, ? extends K> keyMapper, Function<? super T, ? extends U> valueMapper,
BinaryOperator<U> mergeFunction);
// 3
toMap(Function<? super T, ? extends K> keyMapper, Function<? super T, ? extends U> valueMapper,
BinaryOperator<U> mergeFunction, Supplier<M> mapSupplier);
参数含义分别是
keyMapper:Key的映射函数valueMapper:Value的映射函数mergeFunction:当Key冲突时,调用的合并方法mapSupplier:Map 构造器,在需要返回特定的 Map 时使用
还是用上面的例子,如果 List 中 userId 有相同的,使用上面的写法会抛异常
List<User> userList = Lists.newArrayList(
new User().setId("A").setName("张三"),
new User().setId("A").setName("李四"), // Key 相同
new User().setId("C").setName("王五")
);
userList.stream().collect(Collectors.toMap(User::getId, User::getName));
// 异常:
java.lang.IllegalStateException: Duplicate key 张三
at java.util.stream.Collectors.lambda$throwingMerger$114(Collectors.java:133)
at java.util.HashMap.merge(HashMap.java:1245)
at java.util.stream.Collectors.lambda$toMap$172(Collectors.java:1320)
at java.util.stream.ReduceOps$3ReducingSink.accept(ReduceOps.java:169)
at java.util.ArrayList$ArrayListSpliterator.forEachRemaining(ArrayList.java:1374)
at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:481)
at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:471)
at java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:708)
at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
at java.util.stream.ReferencePipeline.collect(ReferencePipeline.java:499)
at Test.toMap(Test.java:17)
...
这时就需要调用第二个重载方法,传入合并函数,如
userList.stream().collect(Collectors.toMap(User::getId, User::getName, (n1, n2) -> n1 + n2));
// 输出结果:
A-> 张三李四
C-> 王五
第四个参数(mapSupplier)用于自定义返回 Map 类型,比如我们希望返回的 Map 是根据 Key 排序的,可以使用如下写法
TreeMap是一个有序的key-value集合,它是通过红黑树实现的,会根据其键(key)进行自然排序。
List<User> userList = Lists.newArrayList(
new User().setId("B").setName("张三"),
new User().setId("A").setName("李四"),
new User().setId("C").setName("王五")
);
userList.stream().collect(
Collectors.toMap(User::getId, User::getName, (n1, n2) -> n1, TreeMap::new)
);
// 输出结果:
A-> 李四
B-> 张三
C-> 王五
坑点1-Map中的key不能重复(可通过BinaryOperator规避)
在使用 Collectors.toMap 时候,Map中的 key 不能重复。如下示例,当有重复的 key,会抛出 IllegalStateException 状态异常。
List<User> userList = Lists.newArrayList(
new User().setId("A").setName("张三"),
new User().setId("A").setName("李四"), // Key 相同
new User().setId("C").setName("王五")
);
userList.stream().collect(Collectors.toMap(User::getId, User::getName));
// 异常:
java.lang.IllegalStateException: Duplicate key 张三
at java.util.stream.Collectors.lambda$throwingMerger$114(Collectors.java:133)
at java.util.HashMap.merge(HashMap.java:1245)
at java.util.stream.Collectors.lambda$toMap$172(Collectors.java:1320)
at java.util.stream.ReduceOps$3ReducingSink.accept(ReduceOps.java:169)
at java.util.ArrayList$ArrayListSpliterator.forEachRemaining(ArrayList.java:1374)
at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:481)
at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:471)
at java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:708)
at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
at java.util.stream.ReferencePipeline.collect(ReferencePipeline.java:499)
at Test.toMap(Test.java:17)
...
查看其源码,可以发现
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);
}
If the mapped keys contains duplicates (according to Object#equals(Object)), an IllegalStateException is thrown when the collection operation is performed. If the mapped keys may have duplicates, use toMap(Function, Function, BinaryOperator) instead.
因此,若 Map 中有重复的 key,建议使用 toMap(Function, Function, BinaryOperator) 方法进行替换。
坑点2-Map中的value不能为null
在使用 Collectors.toMap 时候,Map中的 value 不能为null,否则会抛出 NullPointerException 异常,如下示例。
User user1 = new User("A","张三");
User user2 = new User("D","李四");
User user3 = new User("C",null); //value 为null
List<User> list = new ArrayList<>();
list.add(user1);
list.add(user2);
list.add(user3);
Map<String, String> map = list.stream().collect(Collectors.toMap(User::getId, User::getName, (n1, n2) -> n1, TreeMap::new));
System.out.println(map.keySet());
System.out.println(map.values());
Exception in thread "main" java.lang.NullPointerException
at java.util.Objects.requireNonNull(Objects.java:203)
at java.util.Map.merge(Map.java:1172)
at java.util.stream.Collectors.lambda$toMap$58(Collectors.java:1320)
at java.util.stream.ReduceOps$3ReducingSink.accept(ReduceOps.java:169)
at java.util.ArrayList$ArrayListSpliterator.forEachRemaining(ArrayList.java:1382)
at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:481)
at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:471)
at java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:708)
at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
at java.util.stream.ReferencePipeline.collect(ReferencePipeline.java:499)
at com.lbs0912.java.demo.Solution.main(Solution.java:30)
查看源码可以发现,Collectors.toMap 底层是基于 Map.merge 方法来实现的,而 merge 中 value 是不能为 null 的。如果为 null,就会抛出空指针异常。
Collectors.toMap() internally uses Map.merge() to add mappings to the map. Map.merge() is spec'd not to allow null values, regardless of whether the underlying Map supports null values. This could probably use some clarification in the Collectors.toMap() specifications.
其解决方案为
- 方案1:使用for循环或forEach
Map<String, String> map1 = null;
list.forEach(user -> {
map1.put(user.getId(),user.getName());
});
- 使用 stream 的
collect的重载方法
Map<String, String> map1 = list.stream().collect(HashMap::new,(m,v)-> m.put(v.getId(),v.getName()),HashMap::putAll);