Chaining Optionals in Java 8

Chaining Optionals in Java 8

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();

Chaining two nullable Optional in one pipeline

Let's verify first if the resulting chain is correct, The first step would be breaking the methods into a single chain of Optional method calls.

void doMagic(EntityOne entityOne) {
repoOne.findById(entityOne.getPrimaryKey())
.ifPresent(eOne -> Optional
.ofNullable(eOne)
.map(entityOne::createEntityTwoPrimaryKey)
.flatMap(repoTwo::findById)
.ifPresent(repoTwo::delete));
}

Now we can fold entity -> Optional.ofNullable(entity) with folded ifPresent into a flattened structure:

void doMagic(EntityOne entityOne) {
repoOne.findById(entityOne.getPrimaryKey())
.map(entityOne::createEntityTwoPrimaryKey)
.flatMap(repoTwo::findById)
.ifPresent(repoTwo::delete);
}

So far so good, however, there is one more thing. But there is a dangerous thing, notice this line:

.map(entityOne::createEntityTwoPrimaryKey)

What's wrong? This method reference doesn't call the createEntityTwoPrimaryKey method of the instance captured by the lambda expression but the one passed to the method itself! It'd be equivalent to:

.map(e -> entityOne.createEntityTwoPrimaryKey(e))

Which is not correct as is error-prone to NPE because entityOne can be null. You want to use:

// both are equivalent
.map(e -> e.createEntityTwoPrimaryKey(e))
.map(EntityOne::createEntityTwoPrimaryKey)

So the final chain would look like:

void doMagic(EntityOne entityOne) {
repoOne.findById(entityOne.getPrimaryKey())
.map(EntityOne::createEntityTwoPrimaryKey)
.flatMap(repoTwo::findById)
.ifPresent(repoTwo::delete);
}

Now the transformation is really correct, so let's go back to the question:

If I write like below, There are no compilation errors. But what will happen when repoOne.findById can't find any data in the database? I'm only checking ifPresent() after repoTwo::findById.

Consider the following scenarios:

  • repoOne.findById returns Optional.empty() - No subsequent map and flatMap are called, hence nothing is created. Also no ifPresent is called, hence nothing is deleted.

  • repoOne.findById returns a non-empty Optional but map does - Again, everything from flatMap to the end is not called, hence nothing is created or deleted.

  • Everything proceeding flatMap returns a non-empty Optional, but flatMap does - Here the things start to be interesting because createEntityTwoPrimaryKey is whatever it does, but the removal in ifPresent is not. However, I assume if something is created it would be also most likely be found, so this scenario is really an edge case.

  • The final result depends on the delete method call, however, it is of the void return type. As long as no RuntimeException is thrown, it is safe.

Summary, I find the Optional chain safe. You might want to annotate the method with @Transactional to rollback on possible RuntimeException.

Chaining multiple Java Optionals

You can map the Optional to a completely different value, allowing you to chain null-checks:

Object a, b, c;
....
Optional.ofNullable(a) // null-check for 'a'
.map(x -> b) // null-check for 'b'
.map(x -> c) // null-check for 'c'
.ifPresent(x -> ...) // do something with a,b,c

In your case:

Optional.ofNullable(listing.getLastEntryTime())
.map(x -> listing.getTimingRestrictions())
.filter(x -> !x)
.ifPresent(x -> ... ); // do something with the listing

Optional.ofNullable and method chaining

If you have no idea what can be null, or want to check everything for null, the only way is to chain calls to Optional.map:

If a value is present, apply the provided mapping function to it, and if the result is non-null, return an Optional describing the result. Otherwise return an empty Optional.

As such, if the mapper return null, an empty Optional will be returned, which allows to chain calls.

Optional.ofNullable(insight)
.map(i -> i.getValues())
.map(values -> values.get(0))
.map(v -> v.getValue())
.orElse(0);

The final call to orElse(0) allows to return the default value 0 if any mapper returned null.

Chaining Optionals

public static Optional<Double> getLatestResult(Subcontractor subcontractor) {
return getResult(subcontractor.getLastBusinessYear())
.or(() -> getResult(subcontractor.getPenultimateBusinessYear()));
}

assuming you are running on Java 9+.

ifPresentOrElse unwraps the optional, while you still need to return one.

  1. Try getResult(subcontractor.getLastBusinessYear()).
  2. If it's present, return it.
  3. If it's absent, return another getResult(subcontractor.getPenultimateBusinessYear()).

A Java 8 solution would be

public static Optional<Double> getLatestResult(Subcontractor subcontractor) {
Optional<Double> result = getResult(subcontractor.getLastBusinessYear());
return result.isPresent() ? result : getResult(subcontractor.getPenultimateBusinessYear());
}

What's the most elegant way to combine optionals?

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.

Java chaining map methods and using optional

Polishing the code by gamgoon a little bit:

    Set<String> result = Optional.of(method1())
.map(x -> x.get("A"))
.map(y -> y.get("B"))
.orElse(Collections.emptySet());
System.out.println(result);

Output in case either key is not in a map where looked up:

[]

You may recognize the result of printing an empty collection. In places where you want a collection, you should not accept a null. Use an empty collection to signify that there are no elements. In the same vein I have assumed that your method1() may return an empty map but not null. So we don’t need ofNullable() when converting into an Optional — the simple of() is fine.

Optional orElse Optional in Java

This is part of JDK 9 in the form of method 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, this post I wrote, or ticket JDK-8080418 where this method was introduced.

How to use Java 8 Optionals, performing an action if all three are present?

I think to stream the three Optionals is an overkill, why not the simple

if (maybeTarget.isPresent() && maybeSourceName.isPresent() && maybeEventName.isPresent()) {
...
}

In my eyes, this states the conditional logic more clearly compared to the use of the stream API.



Related Topics



Leave a reply



Submit