Introduction
Java Optional
is a container class that encapsulates an optional value, which was introduced by Java 8. However, I still see lots of colleagues not leveraging it in their work. Maybe it’s too hard to use? Or maybe we don’t see the the beauty of this feature? Let’s dive into it.
What’s wrong with following code?
Let’s take a look the following Java code:
Optional<Dog> dogOption = Optional.ofNullable(dogService.getDog(...));
if (!dogOption.isPresent()) {
...
}
Sometimes we’ll see nested Optional
, which requires nested if
statements to check the value existence. So why do we need Optional
instead of simply a null
check? Let’s first look at the APIs this class is exposing.
APIs
Create Optional object
Optional<String> empty = Optional.empty(); // create empty Optional object
Optional<String> stringOptional = Optional.of("abc"); // the argument cannot be null
Optional<String> nullableStringOptional = Optional.ofNullable(null); // the argument can be null
Check If optional object has value
Optional<String> stringOptional = Optional.of("abc");
assertTrue(stringOptional.isPresent());
Optional<String> nullableStringOptional = Optional.of(null);
assertFalse(nullableStringOptional.isPresent());
Provide default value
There’re 2 ways to provide a default value if the value inside Optional
is an empty value: orElse(T value)
, OrElseGet(Supplier<? extends T> supplier)
.
String name = Optional.ofNullable(null).orElse("default name");
// or
String name = Optional.ofNullable(null).orElseGet(() -> "default name");
What’s the difference between the 2? Let’s say the optional has a non-empty value:
String name = Optional.ofNullable("abc").orElse(getDefaultName());
String name = Optional.ofNullable("abc").orElseGet(this::getDefaultName());
In this example, getDefaultName()
will be executed by orElse()
, but won’t be executed by orElseGet()
.
Throw exception if value is empty
String name = Optional.ofNullable(null).orElseThrow(IllegalArgumentException::new);
Get value from Optional object
String name = Optional.of("abc").get();
But if Optional
has empty value, will throw NoSuchElementException
when calling get()
method.
Filter
Optional<String> stringOptional = Optional.of("123");
assertTrue(stringOptional.filter(s -> s.equals("123")).isPresent());
The filter
method will return non-empty optional if the value meets the predicate, and empty optional otherwise.
Map
If the value inside Optional
is not null, will map the value to something else. Otherwise, return an empty Optional
object.
int size = Optional.of("123").map(String::length).orElse(0);
assertEquals(3, size);
Flatmap
It’s similar to map
, but won’t automatically apply an Option
wrapper around the value. Instead, it’s asking the parameter function to return an Option
object. Thus, this method is good at transform Optional<T>
to Optional<R>
.
How to use Optional?
We are trying to get a nested value, say a state name within an address. If we do it in non-optional way and need to avoid null pointer exception, the code will be like following:
User user = getUser(userId);
if (user != null) {
Address address = user.getAddress();
if (address != null) {
State state = address.getState();
if (state != null) {
return state.getName();
}
}
}
throw new NoSuchElementException();
If we change the code to follow the “Optional” way, it will be as following:
Optional.ofNullable(getUser(userId))
.map(user -> user.getAddress())
.map(address -> address.getState())
.map(state -> state.getName())
.orElseThrow(NoSuchElementException::new);
So if we use Optional
, we see the code is more concise and elegant. Even if user
, address
or state
is null
, we don’t do any null
check, and still, this code won’t throw null pointer exception.
A question you may ask is: If user
is null, will the following map
statements be executed? The answer is yes. Let’s take a peek at source code:
public <U> Optional<U> map(Function<? super T, ? extends U> mapper) {
Objects.requireNonNull(mapper);
if (isEmpty()) {
return empty();
} else {
return Optional.ofNullable(mapper.apply(value));
}
}
As we can see, if the value is empty, the map method will return an empty object (null) to keep the chain operations proceeding.
Summary
This article talks about how to properly use Java Optional
. Optional
provides a way to clearly represent “no result” for method return types. Try to leverage its API and avoid explict null check. Of course, don’t overly use it such as mark all class fields as Optional
.