解决Java Collectors.toMap 方法 value为null不支持的问题
问题描述
在需要把List转换为Map时,通常会使用到Collectors.toMap 方法来转换,但如果转换后的Map的Value有null 则执行时会抛出NullPointerException 异常。
演示代码:
static class Data{
int key;
String value;
public Data(int key, String value) {
this.key = key;
this.value = value;
}
}
@Test
public void test_toMap(){
List<Data> dataList = List.of(
new Data(1,"A"),
new Data(2,"B"),
new Data(3,"C"),
new Data(4,null),
new Data(5,null)
);
Map<Integer,String> dataMap = dataList.stream().collect(Collectors.toMap(item->item.key,item->item.value));
}
执行的异常内容:
java.lang.NullPointerException
at java.base/java.util.Objects.requireNonNull(Objects.java:221)
at java.base/java.util.stream.Collectors.lambda$uniqKeysMapAccumulator$1(Collectors.java:176)
at java.base/java.util.stream.ReduceOps$3ReducingSink.accept(ReduceOps.java:169)
at java.base/java.util.AbstractList$RandomAccessSpliterator.forEachRemaining(AbstractList.java:720)
at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:484)
at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:474)
at java.base/java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:913)
at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
at java.base/java.util.stream.ReferencePipeline.collect(ReferencePipeline.java:511)
原因
Collectors.toMap 有实际有两个实现
public static <T, K, U>
Collector<T, ?, Map<K,U>> toMap(Function<? super T, ? extends K> keyMapper,
Function<? super T, ? extends U> valueMapper) {
return new CollectorImpl<>(HashMap::new,
uniqKeysMapAccumulator(keyMapper, valueMapper),
uniqKeysMapMerger(),
CH_ID);
}
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> mapFactory) {
BiConsumer<M, T> accumulator
= (map, element) -> map.merge(keyMapper.apply(element),
valueMapper.apply(element), mergeFunction);
return new CollectorImpl<>(mapFactory, accumulator, mapMerger(mergeFunction), CH_ID);
}
其中第一个实现,会在uniqKeysMapAccumulator(keyMapper, valueMapper) 方法中通过Objects.requireNonNull(valueMapper.apply(element)) 控制value不能为null。
而第二个实现,最终会会调用Map.merge 方法来合并value ,这个方法里也会通过Objects.requireNonNull(value); 来控制value不能为null。
解决方案
因此Collectors.toMap 方法是无法解决value为null 抛出异常的,但我们实现list转map的需求还是存在的,那么对于这类需求,可以选择另一种方式实现,就是用Collectors.groupingBy 。
实现方案 1
默认的Collectors.groupingBy 返回的是Map<Key,List<Value>> 形式的结果,但我们需要返回的Map<Key,Value> 形式的结果。因此需要对需要做额外的一些处理。
先给出结果demo
@Test
public void test_groupByMap(){
List<Data> dataList = List.of(
new Data(1,"A"),
new Data(2,"B"),
new Data(3,"C"),
new Data(4,null),
new Data(5,null)
);
Map<Integer,String> dataMap = dataList.stream()
.collect(Collectors.groupingBy(item->item.key,
HashMap::new,
Collectors.reducing(null,item->item.value,(v1,v2)->v2)
)
);
}
原理就是利用额外的Collectors.reducing 来做收集处理,将最终的List<Value> 转换成 Value 。
实现方案 2
学到另一种方案,调用Stream 的另一个collect 方法。
<R> R collect(Supplier<R> supplier,
BiConsumer<R, ? super T> accumulator,
BiConsumer<R, R> combiner);
public void test_toMap4(){
List<Data> dataList = List.of(
new Data(1,"A"),
new Data(2,"B"),
new Data(3,"C"),
new Data(4,null),
new Data(5,null)
);
Map<Integer,String> dataMap = dataList.stream()
.collect(HashMap::new,(map,item)->map.put(item.key,item.value),(map1,map2)->map1.putAll(map2)
);
}
当然(map1,map2)->map1.putAll(map2) 也可以简化成HashMap::putAll
|