Optional orElse Optional in Java

JavaLambdaJava 8OptionalJava 9

Java Problem Overview


I've been working with the new Optional type in Java 8, and I've come across what seems like a common operation that isn't supported functionally: an "orElseOptional"

Consider the following pattern:

Optional<Result> resultFromServiceA = serviceA(args);
if (resultFromServiceA.isPresent) return result;
else {
    Optional<Result> resultFromServiceB = serviceB(args);
    if (resultFromServiceB.isPresent) return resultFromServiceB;
    else return serviceC(args);
}

There are many forms of this pattern, but it boils down to wanting an "orElse" on an optional that takes a function producing a new optional, called only if the current one does not exist.

It's implementation would look like this:

public Optional<T> orElse(Supplier<Optional<? extends T>> otherSupplier) {
    return value != null ? this : other.get();
}

I'm curious if there's a reason such a method doesn't exist, if I'm just using Optional in an unintended way, and what other ways people have come up with to deal with this case.

I should say that I think that solutions involving custom utility classes/methods aren't elegant because people working with my code won't necessarily know they exist.

Also, if anyone knows, will such a method be included in JDK 9, and where might I propose such a method? This seems like a pretty glaring omission to the API to me.

Java Solutions


Solution 1 - Java

This is part of JDK 9 in the form of or, which takes a Supplier<Optional<T>>. Your example would then be:

return serviceA(args)
    .or(() -> serviceB(args))
    .or(() -> serviceC(args));

For details see the Javadoc or this post I wrote.

Solution 2 - Java

The cleanest “try services” approach given the current API would be:

Optional<Result> o = Stream.<Supplier<Optional<Result>>>of(
    ()->serviceA(args), 
    ()->serviceB(args), 
    ()->serviceC(args), 
    ()->serviceD(args))
.map(Supplier::get)
.filter(Optional::isPresent)
.map(Optional::get)
.findFirst();

The important aspect is not the (constant) chain of operations you have to write once but how easy it is to add another service (or modify the list of services in general). Here, adding or removing a single ()->serviceX(args) is enough.

Due to the lazy evaluation of streams, no service will be invoked if a preceding service returned a non-empty Optional.


Starting with Java 9, you can simplify the code to

Optional<Result> o = Stream.<Supplier<Optional<Result>>>of(
    ()->serviceA(args), 
    ()->serviceB(args), 
    ()->serviceC(args), 
    ()->serviceD(args))
.flatMap(s -> s.get().stream())
.findFirst();

though this answer already contains an even simpler approach for JDK 9.

JDK 16 offers the alternative

Optional<Result> o = Stream.<Supplier<Optional<Result>>>of(
    ()->serviceA(args), 
    ()->serviceB(args), 
    ()->serviceC(args), 
    ()->serviceD(args))
.<Result>mapMulti((s,c) -> s.get().ifPresent(c))
.findFirst();

though this approach might be more convenient with service methods accepting a Consumer rather than returning a Supplier.

Solution 3 - Java

It's not pretty, but this will work:

return serviceA(args)
  .map(Optional::of).orElseGet(() -> serviceB(args))
  .map(Optional::of).orElseGet(() -> serviceC(args))
  .map(Optional::of).orElseGet(() -> serviceD(args));

.map(func).orElseGet(sup) is a fairly handy pattern for use with Optional. It means "If this Optional contains value v, give me func(v), otherwise give me sup.get()".

In this case, we call serviceA(args) and get an Optional<Result>. If that Optional contains value v, we want to get Optional.of(v), but if it is empty, we want to get serviceB(args). Rinse-repeat with more alternatives.

Other uses of this pattern are

  • .map(Stream::of).orElseGet(Stream::empty)
  • .map(Collections::singleton).orElseGet(Collections::emptySet)

Solution 4 - Java

Perhaps this is what you're after: https://stackoverflow.com/questions/24599996/get-value-from-one-optional-or-another

Otherwise, you may want to have a look at Optional.orElseGet. Here's an example of what I think that you're after:

result = Optional.ofNullable(serviceA().orElseGet(
                                 () -> serviceB().orElseGet(
                                     () -> serviceC().orElse(null))));

Solution 5 - Java

Assuming you're still on JDK8, there are several options.

Option#1: make your own helper method

E.g.:

public class Optionals {
    static <T> Optional<T> or(Supplier<Optional<T>>... optionals) {
        return Arrays.stream(optionals)
                .map(Supplier::get)
                .filter(Optional::isPresent)
                .findFirst()
                .orElseGet(Optional::empty);
    }
}

So that you could do:

return Optionals.or(
   ()-> serviceA(args),
   ()-> serviceB(args),
   ()-> serviceC(args),
   ()-> serviceD(args)
);

Option#2: use a library##

E.g. google guava's Optional supports a proper or() operation (just like JDK9), e.g.:

return serviceA(args)
  .or(() -> serviceB(args))
  .or(() -> serviceC(args))
  .or(() -> serviceD(args));

(Where each of the services returns com.google.common.base.Optional, rather than java.util.Optional).

Solution 6 - Java

This is looks like a good fit for pattern matching and a more traditional Option interface with Some and None implementations (such as those in Javaslang, FunctionalJava) or a lazy Maybe implementation in cyclops-react.I'm the author of this library.

With cyclops-react you can also use structural pattern matching on JDK types. For Optional you can match on the present and absent cases via the visitor pattern. it would look something like this -

  import static com.aol.cyclops.Matchables.optional;

  optional(serviceA(args)).visit(some -> some , 
                                 () -> optional(serviceB(args)).visit(some -> some,
                                                                      () -> serviceC(args)));


Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionYona AppletreeView Question on Stackoverflow
Solution 1 - JavaNicolai ParlogView Answer on Stackoverflow
Solution 2 - JavaHolgerView Answer on Stackoverflow
Solution 3 - JavaMishaView Answer on Stackoverflow
Solution 4 - JavaaioobeView Answer on Stackoverflow
Solution 5 - JavaSheepyView Answer on Stackoverflow
Solution 6 - JavaJohn McCleanView Answer on Stackoverflow