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不能重复
在使用 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);
复制代码