Optional orElse Optional in 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
.
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)