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 checkingifPresent()
afterrepoTwo::findById
.
Consider the following scenarios:
repoOne.findById
returnsOptional.empty()
- No subsequentmap
andflatMap
are called, hence nothing is created. Also noifPresent
is called, hence nothing is deleted.repoOne.findById
returns a non-emptyOptional
butmap
does - Again, everything fromflatMap
to the end is not called, hence nothing is created or deleted.Everything proceeding
flatMap
returns a non-emptyOptional
, butflatMap
does - Here the things start to be interesting becausecreateEntityTwoPrimaryKey
is whatever it does, but the removal inifPresent
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 thevoid
return type. As long as noRuntimeException
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.
- Try
getResult(subcontractor.getLastBusinessYear())
. - If it's present, return it.
- 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 Optional
s 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
Raising a Number to a Power in Java
How to Create Splash Screen with Transparent Background in Javafx
Do Any Jvm's Jit Compilers Generate Code That Uses Vectorized Floating Point Instructions
Java Regex for Support Unicode
Sharing Session Data Between Contexts in Tomcat
Difference Between Shutdown and Shutdownnow of Executor Service
Count Words in a String Method
Strange Java Null Behavior in Method Overloading
Java Jdbc MySQL Exception: "Operation Not Allowed After Resultset Closed"
How to Read a File from a Jar File
How to Make Lombok and Aspectj Work Together
Parsing JSON with Gson, Object Sometimes Contains List Sometimes Contains Object
What Is the Easiest Way to Ignore a JPA Field During Persistence
Java's Date(...) Constructor Is Deprecated; What Does That Mean
How to Keep the Iteration Order of a List When Using Collections.Tomap() on a Stream