问题
java8提供了Stream API,配合Lambda表达式,让开发者能对集合对象进行便利、高效的操作。
在日常业务开发中,有个经常用到的场景是将List类型对象转换为Map类型对象,方便后续操作。
在java8之前,这种转换需要先new一个Map对象,遍历list然后通过Map#put
来初始化。
使用java8后,可方便的使用list.stream().collect(Collectors.toMap(...))
进行转换。
然而这种转换可能会遇到转换失败程序报错的情况,这里总结了常见的2种报错的例子和解决思路。
示例和分析
1.java.lang.IllegalStateException
场景:有重复的key
例:
public static void main(String[] args) {
Person p1 = new Person("aa", 18);
Person p2 = new Person("bb", 20);
Person p3 = new Person("cc", 18);
List<Product> list = Stream.of(p1, p2, p3).collect(Collectors.toList());
Map<Integer, Person> map = list.stream().collect(Collectors.toMap(p -> p.getAge(), p -> p);
System.out.println(map);
}
@NoArgsConstructor
@AllArgsConstructor
@Data
static class Person {
private String name;
private Integer age;
}
Collectors.toMap
里使用里使用Person
类的age
字段为key,由于p1
和p3
的age
值都为18,上面程序运行错误信息如下:
Exception in thread "main" java.lang.IllegalStateException: Duplicate key StreamToMapTest1.Person(name=aa, age=18)
at java.util.stream.Collectors.lambda$throwingMerger$0(Collectors.java:133)
at java.util.HashMap.merge(HashMap.java:1254)
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:482)
at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:472)
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)
解决思路:使用Collectors.toMap
的3个参数的重载方法,第3个参数指定mergeFunction
Map<Integer, Person> map = list.stream().collect(Collectors.toMap(p -> p.getAge(), p -> p, (o, n) -> n));
(o, n) -> n)
表示遇到重复需要合并时使用新值;
如果写(o, n) -> o)
则是使用旧值;
o和n是old和new的缩写,便于理解,也可以用其它变量名。
2.java.lang.NullPointerException
场景:value里有null值
例:
public static void main(String[] args) {
Product p1 = new Product("1001", "aaa");
Product p2 = new Product("1002", null);
Product p3 = new Product("1003", "bbb");
List<Product> list = Stream.of(p1, p2, p3).collect(Collectors.toList());
Map<String, String> map = list.stream().collect(Collectors.toMap(p -> p.getProductCode(), p -> p.getBarCode(), (o, n) -> n));
System.out.println(map);
}
@NoArgsConstructor
@AllArgsConstructor
@Data
private static class Product {
private String productCode;
private String barCode;
}
Collectors.toMap
里使用里使用Product
类的barCode
字段为value,由于p2
和barCode
值是null
,上面程序运行错误信息如下:
Exception in thread "main" java.lang.NullPointerException
at java.util.HashMap.merge(HashMap.java:1225)
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:482)
at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:472)
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)
解决思路:
1.考虑把value为null
的通过filter
过滤掉再转换map
注:这是思路适用于不需要null的场景,可能有的场景map里需要保留null值,然后对map做进一步处理,可考虑思路2。
2.使用stream().collect
的重载方法来创建Map
HashMap<Object, Object> map = list.stream().collect(HashMap::new, (m, p) -> m.put(p.getProductCode(), p.getBarCode()), HashMap::putAll);
参考
- Java8中stream()操作toMap()时Duplicate key问题解决 https://blog.csdn.net/weixin_44422604/article/details/119888769
- Collectors.toMap方法value值为null时的解决方案 https://blog.csdn.net/qq_23204557/article/details/127558822