Core Java

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

  1. it doesn’t look like it has been considered when Optional was designed, and
  2. 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.

Published by Leo Leung under CC-BY 2.0.
Published by Leo Leung under CC-BY 2.0.

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 a Stream.
  • Use Optional::or to replace an empty Optional with the result of a call returning another Optional.
  • With Optional::ifPresentOrElse you can do both branches of an isPresent-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.

Nicolai Parlog

Nicolai is a thirty year old boy, as the narrator would put it, who has found his passion in software development. He constantly reads, thinks, and writes about it, and codes for a living as well as for fun.Nicolai is the editor of SitePoint's Java channel, writes a book about Project Jigsaw, blogs about software development on codefx.org, and is a long-tail contributor to several open source projects. You can hire him for all kinds of things.
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Back to top button