空指针异常:元素无法映射到空键。

27

我已阅读主题:

Collectors.groupingBy不接受空键

但我不明白如何将它应用于我的问题:

我的代码:

Map<String, List<MappingEntry>> mappingEntryMap = mapping.getMappingEntries()
                .stream()
                .collect(Collectors.groupingBy(MappingEntry::getMilestone, Collectors.mapping(e -> e, Collectors.toList())));

对我来说,MappingEntry::getMilestone 有时可能返回空值。虽然这对我的情况没有问题,但我还是想指出:

Caused by: java.lang.NullPointerException: element cannot be mapped to a null key
    at java.util.Objects.requireNonNull(Objects.java:228)
    at java.util.stream.Collectors.lambda$groupingBy$45(Collectors.java:907)
    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)

我该如何避免这个异常?


4
先过滤空值,怎么样? - Eugene
1
@Eugene,对于我的情况来说是不可行的。我不能丢失数据。这个链接主题中描述的方法也不可行。 - gstackoverflow
1
另外,Collectors.mapping(e -> e, Collectors.toList()) 可以简化为 Collectors.toList() - Lino
2
@Lino... 当使用Collectors.groupingBy时,可以省略Collectors.toList() - Holger
请检查我的答案,解决方案使用 Optional 而不是原始的 null。 - Mateusz
显示剩余4条评论
7个回答

23

使用Collectors.toMap替代,并指定使用HashMap(因为它允许一个空键)

 Collectors.toMap(
       MappingEntry::getMilestone,
       x -> {
           List<MappingEntry> list = new ArrayList<>();
           list.add(x);
           return list;
       },
       (left, right) -> {
            left.addAll(right);
            return left;
       },
       HashMap::new

)

不是一个优美的解决方案。你改变了数据结构和逻辑。不值得成为最佳答案。 - Rajesh Paul
1
@RajeshPaul,至少有17个人不同意你的观点。 - Eugene
@RajeshPaul 数据结构仍然是Map<String, List<MappingEntry>>,逻辑也是一样的。 - undefined

17

假设您想保留MappingEntry对象,无论何时getMilestone()null还是non-null,并且知道当一个特定的合同不满足条件时会抛出NullPointerException,那么我们可以通过使用替换键来将具有null里程碑的MappingEntry对象分组,同时将其他MappingEntry对象按照它们应该的方式分组,从而避免这种情况。

Map<String, List<MappingEntry>> mappingEntryMap = 
             mapping.getMappingEntries()
                    .stream()
                    .collect(groupingBy(m -> m.getMilestone() == null ?
                                  "absentMilestone" : m.getMilestone()));

这里的技巧是使用三元运算符,它提供了一个键来将所有具有缺失里程碑的MappingEntry对象分组到单个组中,如果里程碑不缺失,则我们可以按其值进行分组,如您所期望的那样。

The trick here is to use a ternary operator which provides a key to group all the MappingEntry objects that have an absent milestone into a single group. If the milestone is not absent, then we can group by its value as expected.

1
这是绝对正确的答案,值得被选为最佳答案。感谢 @Ousmane D。 - Rajesh Paul
@RajeshPaul 这绝对不是一个正确的答案。它改变了原始代码的行为,如果getMilestone()返回"absentMilestone",那么无论是返回"absentMilestone"的对象还是返回null的对象都会进入同一个桶中。 - undefined

2
最好使用Stream.collect():
mapping.getMappingEntries().stream()
  .collect(
    HashMap::new,
    (m,v) -> m.computeIfAbsent(v.getMilestone, tmp -> new ArrayList<>()).add(v),
    (m1, m2) -> {}
  );

2

我认为最好的解决方案是:

使用 Optional.empty() 代替传递 null 值。

Map<Optional<String>, List<MappingEntry>> mappingEntryMap = mapping.getMappingEntries()
            .stream()
            .collect(Collectors.groupingBy(Optional::ofNullable, Collectors.mapping(e -> e, Collectors.toList())));

唉...使用支持空键的HashMap有什么意义呢,如果我们仍然必须使用Optional<X>键,因为愚蠢的groupingBy在分类器返回null时会抛出NPE?这个检查可能就可以省略了,这样做会使一切变得更容易,而且不会损失安全性(只有在将空键放入映射中时,如果映射不支持空键,才会抛出NPE)。但不幸的是,这似乎是最好的解决方案,紧接着将Map<Optional<X>>迅速转换为具有空键的Map<X> - undefined

1
请使用 Optional 来完成此操作:
var mappingEntryMap = mapping.getMappingEntries().stream()
    .collect(groupingBy(entry -> ofNullable(entry.getMilestone());

然后:

var emptyMilestoneList = mappingEntryMap.get(Optional.empty());

0
你可以使用Collector.of方法来指定自定义收集器,并使用HashMap作为结果映射,因为根据Java-8文档的描述,HashMap允许空值和空键。
Collector.of(
    HashMap::new, 
    (map, e) -> map.computeIfAbsent(e.getMileStone(), k -> new ArrayList<>()).add(e), 
    (left, right) -> {
         right.forEach((key,list) -> left.computeIfAbsent(key, k -> new ArrayList<>()).addAll(list));
         return left;
     })
)

0

使用过滤器并仅获取非空数据。

例如

            Map<String, List<Entity>> map = list
                .stream()
                .filter(entity -> entity.getZoneRefId()!=null)
                .collect(
                        Collectors.
                                groupingBy(
                                        Entity::getZoneName));
        

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接