Home > Software design >  Java stream map-if-or-else with predicate and alternative value
Java stream map-if-or-else with predicate and alternative value

Time:09-10

Do Java streams have a convenient way to map based upon a predicate, but if the predicate is not met to map to some other value?

Let's say I have Stream.of("2021", "", "2023"). I want to map that to Stream.of(Optional.of(Year.of(2021)), Optional.empty(), Optional.of(Year.of(2023))). Here's one way I could do that:

Stream<String> yearStrings = Stream.of("2021", "", "2023");
Stream<Optional<Year>> yearsFound = yearStrings.map(yearString ->
    !yearString.isEmpty() ? Year.parse(yearString) : null)
        .map(Optional::ofNullable);

But here is what I would like to do, using a hypothetical filter-map:

Stream<String> yearStrings = Stream.of("2021", "", "2023");
Stream<Optional<Year>> yearsFound = yearStrings.mapIfOrElse(not(String::isEmpty),
    Year::parse, null).map(Optional::ofNullable);

Of course I can write my own mapIfOrElse(Predicate<>, Function<>, T) function to use with Stream.map(), but I wanted to check if there is something similar in Java's existing arsenal that I've missed.

CodePudding user response:

There is not a very much better way of doing it than you have it - it might be nicer if you extracted it to a method, but that's really it.

Another way might be to construct Optionals from all values, and then use Optional.filter to map empty values to empty optionals:

yearStreams.map(Optional::of)
           .map(opt -> opt.filter(Predicate.not(String::isEmpty)));

Is this better? Probably not.

Yet another way would be to make use of something like Guava's Strings.emptyToNull (other libraries are available), which turns your empty strings into null first; and then use Optional.ofNullable to turn non-nulls and nulls into non-empty and empty Optionals, respectively:

yearStreams.map(Strings::emptyToNull)
           .map(Optional::ofNullable)

CodePudding user response:

You can just simply use filter to validate and then only map

Stream<Year> yearsFound = yearStrings.filter(yearString->!yearString.isEmpty()).map(Year::parse)
    

CodePudding user response:

It's hardly possible to combine all these actions smoothly in well-readable way within a single stream operation.

Here's a weird method-chaining with Java 16 mapMulti():

Stream<Optional<Year>> yearsFound = yearStrings
    .mapMulti((yearString, consumer) -> 
        Optional.of(yearString).filter(s -> !s.isEmpty()).map(Year::parse)
                .ifPresentOrElse(year -> consumer.accept(Optional.of(year)),
                    () -> consumer.accept(Optional.empty()))
    );
  • Related