Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Chaining Optionals in Java 8

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.

like image 548
piler Avatar asked Feb 14 '15 10:02

piler


People also ask

Does Java have optional chaining?

The need for Optional In any Java application, it is a common pattern to use method chaining to de-reference object properties. This often leads to the NullPointerException whenever there is a null object reference.

What are optionals in Java 8?

Advertisements. Optional is a container object used to contain not-null objects. Optional object is used to represent null with absent value. This class has various utility methods to facilitate code to handle values as 'available' or 'not available' instead of checking null values.

Why is null better than optional?

In a nutshell, the Optional class includes methods to explicitly deal with the cases where a value is present or absent. However, the advantage compared to null references is that the Optional class forces you to think about the case when the value is not present.


4 Answers

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();
like image 162
Sauli Tähkäpää Avatar answered Oct 23 '22 21:10

Sauli Tähkäpää


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);
like image 32
Indrek Ots Avatar answered Oct 23 '22 22:10

Indrek Ots


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.

like image 47
Alexis C. Avatar answered Oct 23 '22 23:10

Alexis C.


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))));
like image 7
epox Avatar answered Oct 23 '22 22:10

epox