1

My stream function sometime returns null, when I do collect them How to delete that null return?

versions.stream().map(vs->{
        if(vs.matches("^matched string$")) {
                ...
                return new VersionNumber(tmp[0], tmp[1], tmp[2]));
        }
        return null;
    }).flatMap(Optional::stream).collect(Collectors.toList());

For this stream functions, if all matched is false, I mean if all the function inside the map() method, it will rise NullPointException.

How to make this stream not rise an exception and when all elements are null make it to return an empty list or null?

Alexander Ivanchenko
  • 25,667
  • 5
  • 22
  • 46
user504909
  • 9,119
  • 12
  • 60
  • 109
  • 2
    Can you please explain the logic: `map()` produces a stream of `VersionNumber` (not optionals, because you're returning `new VersionNumber()`), and it's being followed by `flatMap(Optional::stream)`, which implies that stream elements expected to be of type optional ? – Alexander Ivanchenko Jun 30 '22 at 17:53
  • If a function in the map returns `Optional` than you should not return if condition wasn't meat - return an **empty optional** instead. – Alexander Ivanchenko Jun 30 '22 at 18:02
  • If you eliminate `flatMap(Optional::stream)` that's expecting an Optional that you're not providing, then this question addresses how to filter nulls from a stream - https://stackoverflow.com/questions/17081063/. – Andy Thomas Jun 30 '22 at 20:14

3 Answers3

3

You can filter afterward

versions.stream().map(vs->{
        if(vs.matches("^matched string$")) {
                ...
                return new VersionNumber(tmp[0], tmp[1], tmp[2]));
        }
        return null;
    }).filter(Objects::nonNull).flatMap(Optional::stream).collect(Collectors.toList());

But that is wiser to filter the sooner

versions.stream().filter(vs -> vs.matches("^matched string$"))
                 .map(vs->{
                           ...
                           return new VersionNumber(tmp[0], tmp[1], tmp[2]));
    }).flatMap(Optional::stream).collect(Collectors.toList());
azro
  • 53,056
  • 7
  • 34
  • 70
2

if all matched is false, I mean if all the function in .map method, it will rise a NullPointException

Since you're getting a NullPointerException that means that your code compiles and running. Therefore, I assume that the "code" inside the map() operation isn't your actual code because we can't treat elements in the of Stream<VersionNumber> as optionals.

flatMap(Optional::stream) will not cause a compilation error only if preceding map() produces a result of type Optional<VersionNumber>.

Hence, my suggestion is simple: never return null in place of optional. It's completely wrong when an optional object might be null, it should either contain a value or be empty, but it should never be null.

And as @Andy Thomas has pointed out in the comment, there's no reason to utilize Optional if the code inside the map() really creates the object on the spot like new VersionNumber(tmp[0], tmp[1], tmp[2]));. In this case, wrapping the result of the map() with an Optional will be a misuse of optional. Return either VersionNumber instance or null. And then apply a filter:

    .map(vs -> {
            if (vs.matches("^matched string$")) {
            ...
            return new VersionNumber(tmp[0], tmp[1], tmp[2]);
        }
        return null;
    })
    .filter(Objects::nonNull)
    .collect(Collectors.toList());

You have to resort to the usage of optional only if inside the map you're actually not creating VersionNumber as you've shown, but, for instance, making an API call which return an Optional<VersionNumber> if some condition is meat.

    .map(vs -> {
        if (vs.matches("^matched string$")) {
            ...
            return getVersionNumber(tmp[0], tmp[1], tmp[2]); // method returing Optional<VersionNumber>
        }
        return Optional.empty();
    })
    .flatMap(Optional::stream)
    .collect(Collectors.toList());

In such a case, there's no need to apply filtering. Optional.stream() when invoked on an empty optional produces an empty stream.

Alexander Ivanchenko
  • 25,667
  • 5
  • 22
  • 46
0

As of java 16, there is also Stream.mapMulti:

Returns a stream consisting of the results of replacing each element of this stream with multiple elements, specifically zero or more elements.

(emphasis mine)

Using this, your example becomes:

versions.stream().mapMulti((vs, consumer)->{
        if(vs.matches("^matched string$")) {
                ...
                consumer.accept(new VersionNumber(tmp[0], tmp[1], tmp[2]));
        }    
}).collect(Collectors.toList());

Note that the null values are not emitted downstream at all, so there is no need for filtering or intermediate streams for flat mapping.


Another example:

record A(String name, long count) {}

public static void main(String[] args) {
    Stream.of(new A("apple", 1), new A("orange", 3), new A("banana", 0))
        .mapMulti((bucket, consumer) -> {
            for (int i = 0; i < bucket.count(); i++) {
                consumer.accept(bucket.name());
            }
        })
        .forEachOrdered(System.out::println);       
}

Prints:

apple
orange
orange
orange

3 oranges, but no banana

Hulk
  • 6,399
  • 1
  • 30
  • 52