Java 9 Additions To Optional
Wow, people were really interested in Java 9’s additions to the Stream API. Want some more? Let’s look at …
Optional
Optional::stream
This one requires no explanation:
Stream<T> stream();
The first word that comes to mind is: finally! Finally can we easily get from a stream of optionals to a stream of present values!
Given a method Optional findCustomer(String customerId)
we had to do something like this:
public Stream<Customer> findCustomers(Collection<String> customerIds) { return customerIds.stream() .map(this::findCustomer) // now we have a Stream<Optional<Customer>> .filter(Optional::isPresent) .map(Optional::get); }
Or this:
public Stream<Customer> findCustomers(Collection<String> customerIds) { return customerIds.stream() .map(this::findCustomer) .flatMap(customer -> customer.isPresent() ? Stream.of(customer.get()) : Stream.empty()); }
We could of course push that into a utility method (which I hope you did) but it was still not optimal.
Now, it would’ve been interesting to have Optional
actually implement Stream
but
- it doesn’t look like it has been considered when
Optional
was designed, and - that ship has sailed since streams are lazy and
Optional
is not.
So the only option left was to add a method that returns a stream of either zero or one element(s). With that we again have two options to achieve the desired outcome:
public Stream<Customer> findCustomers(Collection<String> customerIds) { return customerIds.stream() .map(this::findCustomer) .flatMap(Optional::stream) } public Stream<Customer> findCustomers(Collection<String> customerIds) { return customerIds.stream() .flatMap(id -> findCustomer(id).stream()); }
It’s hard to say which I like better – both have upsides and downsides – but that’s a discussion for another post. Both look better than what we had to do before.
We can now operate lazily on Optional.
It’s hard to say which I like better – both have upsides and downsides – but that’s a discussion for another post. Both look better than what we had to do before.
We can now operate lazily on Optional.
Another small detail: If we want to, we can now more easily move from eager operations on Optional
to lazy operations on Stream
.
public List<Order> findOrdersForCustomer(String customerId) { return findCustomer(customerId) // 'List<Order> getOrders(Customer)' is expensive; // this is 'Optional::map', which is eager .map(this::getOrders) .orElse(new ArrayList<>()); } public Stream<Order> findOrdersForCustomer(String customerId) { return findCustomer(customerId) .stream() // this is 'Stream::map', which is lazy .map(this::getOrders) }
I think I didn’t have a use case for that yet but it’s good to keep in mind.
Optional::or
Another addition that lets me to think finally! How often have you had an Optional
and wanted to express “use this one; unless it is empty, in which case I want to use this other one”? Soon we can do just that:
Optional<T> or(Supplier<Optional<T>> supplier);
Say we need some customer’s data, which we usually get from a remote service. But because accessing it is expensive and we’re very clever, we have a local cache instead. Two actually, one on memory and one on disk. (I can see you cringe. Relax, it’s just an example.)
This is our local API for that:
public interface Customers { Optional<Customer> findInMemory(String customerId); Optional<Customer> findOnDisk(String customerId); Optional<Customer> findRemotely(String customerId); }
Chaining those calls in Java 8 is verbose (just try it if you don’t believe me). But with Optional::or
it becomes a piece of cake:
public Optional<Customer> findCustomer(String customerId) { return customers.findInMemory(customerId) .or(() -> customers.findOnDisk(customerId)) .or(() -> customers.findRemotely(customerId)); }
Isn’t that cool?! How did we even live without it? Barely, I can tell you. Just barely.
Optional::ifPresentOrElse
This last one, I am less happy with:
void ifPresentOrElse(Consumer<? super T> action, Runnable emptyAction);
You can use it to cover both branches of an isPresent
-if:
public void logLogin(String customerId) { findCustomer(customerId) .ifPresentOrElse( this::logLogin, () -> logUnknownLogin(customerId) ); }
Where logLogin
is overloaded and also takes a customer, whose login is then logged. Similarly logUnknownLogin
logs the ID of the unknown customer.
Now, why wouldn’t I like it? Because it forces me to do both at once and keeps me from chaining any further. I would have preferred this by a large margin:
Optional<T> ifPresent(Consumer<? super T> action); Optional<T> ifEmpty(Runnable action);
The case above would look similar but better:
public void logLogin(String customerId) { findCustomer(customerId) .ifPresent(this::logLogin) .ifEmpty(() -> logUnknownLogin(customerId)); }
First of all, I find that more readable. Secondly it allows me to just have the ifEmpty
branch if I whish to (without cluttering my code with empty lambdas). Lastly, it allows me to chain these calls further. To continue the example from above:
public Optional<Customer> findCustomer(String customerId) { return customers.findInMemory(customerId) .ifEmpty(() -> logCustomerNotInMemory(customerId)) .or(() -> customers.findOnDisk(customerId)) .ifEmpty(() -> logCustomerNotOnDisk(customerId)) .or(() -> customers.findRemotely(customerId)) .ifEmpty(() -> logCustomerNotOnRemote(customerId)) .ifPresent(ignored -> logFoundCustomer(customerId)); }
The question that remains is the following: Is adding a return type to a method (in this case to Optional::ifPresent
) an incompatible change? Not obviously but I’m currently too lazy to investigate. Do you know?
Reflection
To sum it up:
- Use
Optional::stream
to map an Optional to aStream
. - Use
Optional::or
to replace an emptyOptional
with the result of a call returning anotherOptional
. - With
Optional::ifPresentOrElse
you can do both branches of anisPresent-if
.
Very cool!
What do you think? I’m sure someone out there still misses his favorite operation. Tell me about it!
Reference: | Java 9 Additions To Optional from our JCG partner Nicolai Parlog at the CodeFx blog. |