Search code examples
javalambdajava-8java-stream

Using Java 8's Optional with Stream::flatMap


The new Java 8 stream framework and friends make for some very concise Java code, but I have come across a seemingly-simple situation that is tricky to do concisely.

Consider a List<Thing> things and method Optional<Other> resolve(Thing thing). I want to map the Things to Optional<Other>s and get the first Other.

The obvious solution would be to use things.stream().flatMap(this::resolve).findFirst(), but flatMap requires that you return a stream, and Optional doesn't have a stream() method (or is it a Collection or provide a method to convert it to or view it as a Collection).

The best I can come up with is this:

things.stream()
    .map(this::resolve)
    .filter(Optional::isPresent)
    .map(Optional::get)
    .findFirst();

But that seems awfully long-winded for what seems like a very common case.

Anyone have a better idea?


Solution

  • Java 16

    Stream.mapMulti has been added to JDK 16. This enables the following, without intermediate creation of single-element Streams:

    Optional<Other> result =
        things.stream()
              .map(this::resolve)
              .<Other>mapMulti(Optional::ifPresent)
              .findFirst();
    

    Java 9

    Optional.stream has been added to JDK 9. This enables you to do the following, without the need of any helper method:

    Optional<Other> result =
        things.stream()
              .map(this::resolve)
              .flatMap(Optional::stream)
              .findFirst();
    

    Java 8

    Yes, this was a small hole in the API, in that it's somewhat inconvenient to turn an Optional<T> into a zero-or-one length Stream<T>. You could do this:

    Optional<Other> result =
        things.stream()
              .map(this::resolve)
              .flatMap(o -> o.isPresent() ? Stream.of(o.get()) : Stream.empty())
              .findFirst();
    

    Having the ternary operator inside the flatMap is a bit cumbersome, though, so it might be better to write a little helper function to do this:

    /**
     * Turns an Optional<T> into a Stream<T> of length zero or one depending upon
     * whether a value is present.
     */
    static <T> Stream<T> streamopt(Optional<T> opt) {
        if (opt.isPresent())
            return Stream.of(opt.get());
        else
            return Stream.empty();
    }
    
    Optional<Other> result =
        things.stream()
              .flatMap(t -> streamopt(resolve(t)))
              .findFirst();
    

    Here, I've inlined the call to resolve() instead of having a separate map() operation, but this is a matter of taste.