daphshez
daphshez

Reputation: 9638

Java8 streams map - check if all map operations succeeded?

I am trying to map one list to another using streams.

Some elements of the original list fail to map. That is, the mapping function may not be able to find an appropriate new value.

I want to know if any of the mappings has failed. Ideally I would also like to stop the processing once a failure happened.

What I am currently doing is:

For example:

List<String> func(List<String> old, Map<String, String> oldToNew)
{
    List<String> holger = old.stream()
                          .map(oldToNew::get)
                          .filter(Objects::nonNull)
                          .collect(Collectors.toList);

    if (holger.size() < old.size()) {
       // ... appropriate error handling code ... 
    }
    else {
       return holger;
    }
}

This is not very elegant. Also, everything is processed even when the whole thing should fail.

Suggestions for a better way of doing it? Or maybe I should ditch streams altogether and use good old loops?

Upvotes: 2

Views: 1172

Answers (2)

Holger
Holger

Reputation: 298143

There is no best solution because that heavily depends on the use case. E.g. if lookup failures are expected to be unlikely or the error handling implies throwing an exception anyway, just throwing an exception at the first failed lookup within the mapping function might indeed be a good choice. Then, no follow-up code has to care about error conditions.

Another way of handling it might be:

List<String> func(List<String> old, Map<String, String> oldToNew) {
    Map<Boolean,List<String>> map=old.stream()
        .map(oldToNew::get)
        .collect(Collectors.partitioningBy(Objects::nonNull));
    List<String> failed=map.get(false);
    if(!failed.isEmpty())
        throw new IllegalStateException(failed.size()+" lookups failed");
    return map.get(true);
}

This can still be considered being optimized for the successful case as it collects a mostly meaningless list containing null values for the failures. But it has the point of being able to tell the number of failures (unlike using a throwing map function).

If a detailed error analysis has a high priority, you may use a solution like this:

List<String> func(List<String> old, Map<String, String> oldToNew) {
    Map<Boolean,List<String>> map=old.stream()
        .map(s -> new AbstractMap.SimpleImmutableEntry<>(s, oldToNew.get(s)))
        .collect(Collectors.partitioningBy(e -> e.getValue()!=null,
            Collectors.mapping(e -> Optional.ofNullable(e.getValue()).orElse(e.getKey()),
                Collectors.toList())));
    List<String> failed=map.get(false);
    if(!failed.isEmpty())
        throw new IllegalStateException("The following key(s) failed: "+failed);
    return map.get(true);
}

It collects two meaningful lists, containing the failed keys for failed lookups and a list of successfully mapped values. Note that both lists could be returned.

Upvotes: 1

Hank D
Hank D

Reputation: 6471

You could change your filter to Objects::requireNonNull and catch a NullPointerException outside the stream

Upvotes: 0

Related Questions