Chaining Optionals in Java 8
Asked Answered
S

11

110

Looking for a way to chain optionals so that the first one that is present is returned. If none are present Optional.empty() should be returned.

Assuming I have several methods like this:

Optional<String> find1()

I'm trying to chain them:

Optional<String> result = find1().orElse( this::find2 ).orElse( this::find3 );

but of course that doesn't work because orElse expects a value and orElseGet expects a Supplier.

Suffuse answered 14/2, 2015 at 10:32 Comment(1)
The version which expects a Supplier would be .orElseGet().Burthen
F
151

Use a Stream:

Stream.of(find1(), find2(), find3())
    .filter(Optional::isPresent)
    .map(Optional::get)
    .findFirst();

If you need to evaluate the find methods lazily, use supplier functions:

Stream.of(this::find1, this::find2, this::find3)
    .map(Supplier::get)
    .filter(Optional::isPresent)
    .map(Optional::get)
    .findFirst();
Freshen answered 14/2, 2015 at 10:52 Comment(8)
findFirst() actually returns an empty optional if no items are found, so I'll remove the orElse().Toner
Yours is even better than mine - except for readability: amybe the first map could be changed to .map(Supplier::get) and the second one to .map(Optional::get). When you are on it, .filter(Optional::isPresent) would be fine as well, but that doesn't hurt readability as much as the others.Burthen
That works though its kind of a lot of code for a simple problem. Makes me think it should have been built in in the Optional apiSuffuse
One thing that is surely missing from Optional is a converter into Stream, not just for this use case. Then this would be a bit more manageable: Stream.of(opt1,opt2,opt3).flatMap(Optional::stream).findFirst().Silique
To be consistent the second version can be written as Stream.of(this::find1, this::find2, this::find3).Abercrombie
@MarkoTopolnik maybe you are aware of this already, but Optional.stream() is going to be added to JDK9Cropeared
To chain optionals from methods that take parameters... -> #28819006Budget
Wouldn't the application of Supplier::get cause an exception if one of the find Optionals is empty? It seems to me we still need to filter with Optional::isPresent first. Or am I missing something? Maybe I'm confused here.Heel
C
63

Inspired by Sauli's answer, it is possible to use the flatMap() method.

Stream.of(this::find1, this::find2, this::find3)
  .map(Supplier::get)
  .flatMap(o -> o.map(Stream::of).orElseGet(Stream::empty))
  .findFirst();

Converting an Optional into a Stream is cumbersome. Apparently, this is going to be fixed with JDK9. So this could be written as

Stream.of(this::find1, this::find2, this::find3)
  .map(Supplier::get)
  .flatMap(Optional::stream)
  .findFirst();

Update after Java 9 was released

Although the original question was about Java 8, Optional::or was introduced in Java 9. With it, the problem could be solved as follows

Optional<String> result = find1()
  .or(this::find2)
  .or(this::find3);
Cropeared answered 21/12, 2015 at 15:14 Comment(0)
C
52

You could do it like this:

Optional<String> resultOpt = Optional.of(find1()
                                .orElseGet(() -> find2()
                                .orElseGet(() -> find3()
                                .orElseThrow(() -> new WhatEverException()))));

Though I'm not sure it improves readability IMO. Guava provides a way to chain Optionals:

import com.google.common.base.Optional;

Optional<String> resultOpt = s.find1().or(s.find2()).or(s.find3());

It could be another alternative for your problem but does not use the standard Optional class in the JDK.

If you want to keep the standard API, you could write a simple utility method:

static <T> Optional<T> or(Optional<T> first, Optional<T> second) {
    return first.isPresent() ? first : second;
}

and then:

Optional<String> resultOpt = or(s.find1(), or(s.find2(), s.find3()));

If you have a lot of optionals to chains, maybe it's better to use the Stream approach as other mentionned already.

Conner answered 14/2, 2015 at 10:51 Comment(8)
The solution with the utility method is probably as readable as it can get. Too bad java's Optional does not have the same method as the Guava oneSuffuse
or(Optional<T>... opts) would be an improvement. Varargs with generics, though, are an unfortunate mix.Silique
Your first approach could throw an exception even if find1 or find2 would return a value.Abercrombie
Whenever find3 returns an empty Optional.Abercrombie
@zeroflagL Yes it will throw a new WhatEverException in this case (the OP didn't originally precise that he would have an empty optional in this case). Not sure if you can do it in one line, though I don't recommend to use this approach.Conner
You could always do : Optional<String> resultOpt = Optional.ofNullable(s.find1().orElse(s.find2().orElse(s.find3().orElse(null)))); but that would assume that a null value is equivalent to an empty Optional, so it depends on what the op wants.Conner
Maybe there's a misunderstanding. If find1 returns a value (= non-empty Optional) then the return value of find3 simply doesn't matter. But in your first solution an exception can be thrown despite the fact we already have a valid result. In other words: Ideally find3 shouldn't have been called in the first place.Abercrombie
@zeroflagL Ah yes thanks for pointing this out! You can also use orElseGet to have a lazy evaluation. I've updated my answer accordingly. (There's still the use case with 3 empty optional though I don't think it's possible to do it in one line).Conner
R
17

Since Java 9

most likely case the readers are looking for (today)

result = find1()
    .or(this::find2)
    .or(this::find3);

Java 8

result = Optional.ofNullable(find1()
    .orElse(find2()
      .orElse(find3()
        .orElse(null))));

PERFORMANCE: the above Java 8 solution pre-calls find2() and find3() each time, even when (later) find1() evaluation returns non-empty.

UPDATE: Performance optimal Java 8 solution with lazy evaluation (thanks to @Alexander's comment) would be:

result = Optional.ofNullable(find1()
    .orElseGet(() -> find2()
      .orElseGet(() -> find3()
        .orElse(null))));      // <-- null would be the last resort, when all findN are empty.
                               //     #ofNullable (in the 1st line would wrap it to Optional
                               //     and return Optional.empty() as OP requires.
Rhinoscopy answered 31/5, 2022 at 23:30 Comment(3)
@FabienWarniez it's been answered in 2017 already https://mcmap.net/q/88325/-chaining-optionals-in-java-8Quarta
For java 8 it is wrong proposal. All Optional get evaluated, even if the first has a not-nullable value. Usually null values - marker of the illegal state and you should throw an expeption. Check how this work, for instance: String value = Optional.ofNullable("str").orElse(Optional.<String>ofNullable(null).orElseThrow(() -> new IllegalStateException())); And the right solution with lazy evaluation: String value = Optional.ofNullable("str").orElseGet(() -> Optional.<String>ofNullable(null).orElseThrow(() -> new IllegalStateException()));Lagrange
@FabienWarniez, no, the proposal for Java 8 is pretty bad. See my previous comment.Lagrange
J
4
// Java 9+
find1().or(() -> find2()).or(() -> find3());


// Java 8
Optional.ofNullable(
   find1().orElse(
      find2().orElse(
         find3().orElse( null )
)));
Jez answered 28/5, 2021 at 10:41 Comment(1)
I wish they just had .orElseOf(1,2,3) or something like thatBurlburlap
N
3

Based on Alexis C's answer, but without the nesting of orElses

String result = find1()
                   .map(Optional::of)
                   .orElseGet(Foo::find2())
                   .map(Optional::of)
                   .orElseGet(Foo::find3())
                   .orElseThrow(() -> new WhatEverException())

Remove the orElseThrow if you want an Optional<String> as the result instead.

The trick is to wrap each optional returned by the findX to another optional before each orElseGet.

Novice answered 11/12, 2020 at 7:36 Comment(0)
C
1

You can use Java 9's or.

If the functions that provides the data you need return it plain, you can do it like that:

Optional.ofNullable(supplier1())
  .or(() -> Optional.ofNullable(supplier2()))
  .or(() -> Optional.ofNullable(supplier3()))
  .orElse(fallbackValue)

I often statically import ofNullable to ease readability. You can also play with orElse or orElseGet to close the chain if needed.

Now, if the functions that provides the data you need return it wrapped by a Optional already and you cannot or do not want to change the return type, you could do this trick to start the chain:

Optional.empty()
  .or(supplier1)
  .or(supplier2)
  .orElse(fallbackValue)
Cookie answered 26/10, 2023 at 19:6 Comment(0)
B
0

for cascading chaining You could use ifPresentOrElse

find1().ifPresentOrElse( System.out::println, new Runnable() {
  public void run() {
    find2().ifPresentOrElse( System.out::println, new Runnable() {
      public void run() {
        find3().ifPresentOrElse( System.out::println, new Runnable() {
          public void run() {
            System.err.println( "nothing found…" );
          }
        } );
      }
    } );
  }
} );

to do something with the value of the Optional You had to replace the System.out::println with Your Consumer
(different Consumers would also be possible in this solution)

Bingen answered 11/12, 2020 at 19:51 Comment(0)
P
0

My common generic solution for all those issues:

public static <T> T firstMatch(final Predicate<T> matcher, final T orElse, final T... values) {
  for (T t : values) {
    if (matcher.test(t)) {
      return t;
    }
  }
  return orElse;
}

Then you can do:

public static <T> Optional<T> firstPresent(final Optional<T>... values) {
  return firstMatch(Optional::isPresent, Optional.empty(), values);
}
Pinochle answered 17/6, 2021 at 11:48 Comment(0)
D
-2

To perform Optional Chaining First convert Stream to Optional Using either of the two methods

  1. findAny() or findFirst()
  2. min() / max()

Once optional is obtained optional has two more instance method which are also present in Stream class i.e filter and map(). use these on methods and to check output use ifPresent(System.out :: Println)

ex:

Stream s = Stream.of(1,2,3,4);

s.findFirst().filter((a)->a+1).ifPresent(System.out :: Println)

Output is : 2

Degradable answered 21/5, 2018 at 17:48 Comment(2)
First: The filter method must return a boolean. "incompatible types: bad return type in lambda expression int cannot be converted to boolean"Antons
You get a NPE when the input is null. Stream<Integer> s = Stream.of(null,2,3,4); s.findFirst().map((a)->a+1).ifPresent(System.out::println);Antons
B
-3

Maybe one of

    public <T> Optional<? extends T> firstOf(Optional<? extends T> first, @SuppressWarnings("unchecked") Supplier<Optional<? extends T>>... supp) {
        if (first.isPresent()) return first;
        for (Supplier<Optional <? extends T>> sup : supp) {
            Optional<? extends T> opt = sup.get();
            if (opt.isPresent()) {
                return opt;
            }
        }
        return Optional.empty();
    }

    public <T> Optional<? extends T> firstOf(Optional<? extends T> first, Stream<Supplier<Optional<? extends T>>> supp) {
        if (first.isPresent()) return first;
        Stream<Optional<? extends T>> present = supp.map(Supplier::get).filter(Optional::isPresent);
        return present.findFirst().orElseGet(Optional::empty);
    }

will do.

The first one iterates over an array of suppliers. The first non-empty Optional<> is returned. If we don't find one, we return an empty Optional.

The second one does the same with a Stream of Suppliers which is traversed, each one asked (lazily) for their value, which is then filtered for empty Optionals. The first non-empty one is returned, or if no such exists, an empty one.

Burthen answered 14/2, 2015 at 11:0 Comment(2)
Wow, again a -1 w/o notice what could be improved.Burthen
Because the answers above do exactly the same in much more clear and elegant way.Blisse

© 2022 - 2024 — McMap. All rights reserved.