Sebastian Oberhoff
Sebastian Oberhoff

Reputation: 1311

What's the most elegant way to combine optionals?

Here's what I've got so far:

Optional<Foo> firstChoice = firstChoice();
Optional<Foo> secondChoice = secondChoice();
return Optional.ofNullable(firstChoice.orElse(secondChoice.orElse(null)));

This strikes me as both hideous and wasteful. If firstChoice is present I am needlessly computing secondChoice.

There's also a more efficient version:

Optional<Foo> firstChoice = firstChoice();
if(firstChoice.isPresent()) {
 return firstChoice;
} else {
 return secondChoice();
}

Here I can't chain some mapping function to the end without either duplicating the mapper or declaring another local variable. All of this makes the code more complicated than the actual problem being solved.

I'd much rather be writing this:

return firstChoice().alternatively(secondChoice());

However Optional::alternatively obviously doesn't exist. Now what?

Upvotes: 51

Views: 39079

Answers (9)

marstran
marstran

Reputation: 28036

Edit:

From Java 9 and onwards, you can use the .or method on Optional. Like this:

firstChoice().or(this::secondChoice);

Try this:

firstChoice()
    .map(Optional::of)
    .orElseGet(this::secondChoice);

The map method gives you an Optional<Optional<Foo>>. Then, the orElseGet method flattens this back to an Optional<Foo>. The secondChoice method will only be evaluated if firstChoice() returns the empty optional.

Upvotes: 55

M. Justin
M. Justin

Reputation: 21122

Java 9 added the Optional.or​(supplier) method for this sort of situation.

return firstChoice().or(this::secondChoice);

Given methods firstChoice() and secondChoice() which each return Optional<Foo>, the above one-liner uses Optional.or to achieve the desired result.

As desired, this approach only computes secondChoice when firstChoice is empty.

Upvotes: 7

thahgr
thahgr

Reputation: 795

with java 11 you can simply do firstChoice().or(this::secondChoice)

Upvotes: 0

Marthym
Marthym

Reputation: 3219

Maybe something like this:

Optional<String> finalChoice = Optional.ofNullable(firstChoice()
    .orElseGet(() -> secondChoice()
    .orElseGet(() -> null)));

From: Chaining Optionals in Java 8

Upvotes: 6

Pepe-Soft
Pepe-Soft

Reputation: 21

Lazy computations and arbitrary number of Optional elements

Stream.<Supplier<Optional<Foo>>>of(
        this::firstChoice,
        this::secondChoice
).map(
        Supplier::get
).filter(
        Optional::isPresent
).findFirst(
).orElseGet(
    Optional::empty
);

Upvotes: 2

Erik
Erik

Reputation: 7342

I was frustrated enough by the fact that this wasn't supported in java 8, that I switched back to guava's optionals which have or:

public abstract Optional<T> or(Optional<? extends T> secondChoice)

Returns this Optional if it has a value present; secondChoice otherwise.

Upvotes: 3

Tagir Valeev
Tagir Valeev

Reputation: 100209

Here's the generalization of @marstran solution for any number of optionals:

@SafeVarargs
public static <T> Optional<T> selectOptional(Supplier<Optional<T>>... optionals) {
    return Arrays.stream(optionals)
            .reduce((s1, s2) -> () -> s1.get().map(Optional::of).orElseGet(s2))
            .orElse(Optional::empty).get();
}

Test:

public static Optional<String> first() {
    System.out.println("foo called");
    return Optional.empty();
}

public static Optional<String> second() {
    System.out.println("bar called");
    return Optional.of("bar");
}

public static Optional<String> third() {
    System.out.println("baz called");
    return Optional.of("baz");
}

public static void main(String[] args) {
    System.out.println(selectOptional(() -> first(), () -> second(), () -> third()));
}

Output:

foo called
bar called
Optional[bar]

Upvotes: 4

Denis Bazhenov
Denis Bazhenov

Reputation: 9945

Here is a way which works for arbitrary number of Optional's based in a stream API:

return Arrays.asList(firstChoice, secondChoice).stream()
  .filter(Optional::isPresent)
  .map(Optional::get)
  .findFirst().orElse(null);

It's not the shortest one. But more plain and understandable.

Another way is to use firstNonNull() from Guava of commons-lang if you are already using one of those libraries:

firstNonNull(firstChoice.orElse(null), secondChoice.orElse(null));

Upvotes: -1

Suresh Atta
Suresh Atta

Reputation: 121998

You can simply replace that with,

Optional<Foo> firstChoice = firstChoice();
return firstChoice.isPresent()? firstChoice : secondChoice();

The above code won't call unless firstChoice.isPresent() is false.

But you have to be prepare to call both functions to get the desired output. There is no other way to escape the checking.

  • Best case is First choice returning true.
  • Worst case will be First choice returning false, hence another method call for second choice.

Upvotes: 5

Related Questions