24

I have read the topic:

Collectors.groupingBy doesn't accept null keys

But I don't understand how can I apply it for my issue:

my code:

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

For me MappingEntry::getMilestone sometimes can return null. It is ok for my situation but I see:

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)

How can I avoid this exception?

Ousmane D.
  • 54,915
  • 8
  • 91
  • 126
gstackoverflow
  • 36,709
  • 117
  • 359
  • 710

7 Answers7

22

Use Collectors.toMap instead and specify that a HashMap is used (as it allows one null key)

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

)
Eugene
  • 117,005
  • 15
  • 201
  • 306
16

Given that you want to retain the MappingEntry objects regardless of when getMilestone() is null or non-null and knowing that a NullPointerException will be thrown when a particular contract in not met then we can avoid that by using a replacement key to group the MappingEntry objects which have a null milestone and yet group the other MappingEntry objects as they're supposed to be.

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

The trick here is to use a ternary operator which provides a key to group all the MappingEntry objects that have a absent milestone into a single group and if the milestone is not absent then we can group by its value as you'd expect.

Ousmane D.
  • 54,915
  • 8
  • 91
  • 126
2

Better to use Stream.collect():

mapping.getMappingEntries().stream()
  .collect(
    HashMap::new,
    (m,v) -> m.computeIfAbsent(v.getMilestone, tmp -> new ArrayList<>()).add(v),
    (m1, m2) -> {}
  );
Tyler2P
  • 2,324
  • 26
  • 22
  • 31
1

In my opinion the best solution:

Use Optional.empty() instead of pasing null value.

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

Please use Optional for that:

var mappingEntryMap = mapping.getMappingEntries().stream()
    .collect(groupingBy(entry -> ofNullable(entry.getMilestone());

And then:

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

You can use the Collector.of method for this to specify a custom collector and use a HashMap for the resulting map because according to the Java-8 docs of HashMap

[...] permits null values and the null key. [...]

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;
     })
)
Lino
  • 19,604
  • 6
  • 47
  • 65
0

use filter and get only not null data.

like

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