3. Optional
NullPointerExceptions may be the most seen exceptions during Java developers’ daily development. Null reference is considered as The billion dollar mistake by its inventor Tony Hoare. Maybe null should not be introduced into Java language in the first place. But it’s already there, so we have to live with it.
Suppose we have a method with argument val of a non-primitive type, we should check first to make sure the value of val is not null.
null valuepublic void myMethod(Object val) {
if (val == null) {
throw new IllegalArgumentException("val cannot be null!");
}
// Use of val
}
Listing 5.1 shows a common pattern of writing code that takes arguments with possible null values. If a method accepts multiple arguments, all these arguments should be checked. Utility methods like Objects.requireNonNull() can help, but the code is still long and tedious. Another case is for long object references chains, e.g. a.b.c, then all objects in the reference chain need to be checked. Groovy has safe navigation operator ?., but Java doesn’t the same thing.
3.1 What’s Optional
Java 8 introduced a new class Optional<T> to solve the issues with nulls. The idea behind Optional is quite simple. An Optional object is a holder of the actual object which may be null. Client code interacts with the Optional object instead of the actual object. The Optional object can be queried about the existence of the actual object. Although the actual object may be null, there is always an non-null Optional object.
The major benefit of using Optional is to force client code to deal with the situation that the actual object that it wants to use may be null. Instead of using the object reference directly, the Optional object needs to be queried first.
Optional objects are very easy to create. If we are sure the actual object is not null, we can use <T> Optional<T> of(T value) method to create an Optional object, otherwise we should use <T> Optional<T> ofNullable(T value) method. If we just want to create an Optional object which holds nothing, we can use <T> Optional<T> empty().
Optional objectsOptional<String> opt1 = Optional.of("Hello");
Optional<String> opt2 = Optional.ofNullable(str);
Optional<String> opt3 = Optional.empty();
If you pass a null value to Optional.of() method, it throws a NullPointerException.
3.2 Usage of Optional
3.2.1 Simple usage
The simple usage of Optional is to query it first, then retrieve the actual object hold by it. We can use boolean isPresent() method to check if an Optional object holds a non-null object, then use T get() method to get the actual value.
Optionalpublic void myMethod(Optional<Object> holder) {
if (!holder.isPresent()) {
throw new IllegalArgumentException("val cannot be null!");
}
Object val = holder.get();
}
Listing 5.3 is similar with Listing 5.1, except it uses Optional. But the code in Listing 5.3 is still long and tedious.
3.2.2 Chained usage
Since Optionals are commonly used with if..else, Optional class has built-in support for this kind of scenarios.
void ifPresent(Consumer<? super T> consumer) method invokes the specified consumer function when value is present, otherwise it does nothing.
Optional.ifPresentpublic void output(Optional<String> opt) {
opt.ifPresent(System.out::println);
}
T orElse(T other) returns the value if it’s present, otherwise return the specified other object. This other object acts as the default or fallback value.
Optional.orElsepublic String getHost(Optional<String> opt) {
return opt.orElse("localhost");
}
T orElseGet(Supplier<? extends T> other) is similar with orElse(), except a supplier function is invoked to get the value if not present. getPort() method in Listing 5.6 invokes getNextAvailablePort() method to get the port if no value is present.
Optional.orElseGetpublic int getNextAvailablePort() {
int min = 49152;
int max = 65535;
return new Random().nextInt((max - min) + 1) + min;
}
public int getPort(Optional<Integer> opt) {
return opt.orElseGet(this::getNextAvailablePort);
}
<X extends Throwable> T orElseThrow(Supplier<? extends X> exceptionSupplier) returns the value if present, otherwise invokes the supplier function to get the exception to throw.
Optional.orElseThrowpublic void anotherMethod(Optional<Object> opt) {
Object val = opt.orElseThrow(IllegalArgumentException::new);
}
3.2.3 Functional usage
As Optional objects are the holders of actual objects, it’s inconvenient to use when manipulating the actual objects. Optional provides some methods to use it in a functional way.
Optional<T> filter(Predicate<? super T> predicate) filters an Optional object’s value using the specified predicate. If the Optional object holds a value and the value matches the predicate, an Optional object with the value is returned, otherwise an empty Optional object is returned.
Listing 5.8 only outputs a string when it is not empty.
Optional.filterOptional<String> opt = Optional.of("Hello");
opt.filter((str) -> str.length() > 0).ifPresent(System.out::println);
<U> Optional<U> map(Function<? super T,? extends U> mapper) applies the specified mapping function to the Optional’s value if present. If the mapping result is not null, it returns an Optional object with this mapping result, otherwise returns an empty Optional object.
Listing 5.9 outputs the length of input string.
Optional.mapOptional<String> opt = Optional.of("Hello");
opt.map(String::length).ifPresent(System.out::println);
<U> Optional<U> flatMap(Function<? super T,Optional<U>> mapper) is similar with map(), but the mapping function returns an Optional object. The returned Optional object only contains a value when the value is present in both the current Optional object and the mapped Optional object.
flatMap() is very useful when handling long reference chain. For example, considering a Customer class has a method getAddress() to return an Optional<Address> object. The Address class has a method getZipCode() to return an Optional<ZipCode> object. Listing 5.10 uses two chained flatMap() method invocations to get an Optional<ZipCode> object from an Optional<Customer> object. With the use of flatMap(), it’s very easy to create safe chained object references.
Optional.flatMapOptional<Customer> opt = getCustomer();
Optional<ZipCode> zipCodeOptional = opt.flatMap(Customer::getAddress)
.flatMap(Address::getZipCode);
zipCodeOptional.ifPresent(System.out::println);
3.3 How-tos
3.3.1 How to interact with legacy library code before Optional?
Not all library code has been updated to use Java 8 Optional, so when we use third-party libraries, we need to wrap an object using Optional.
public Date getUpdated() {
//Get date from somewhere
}
public void setUpdated(Date date) {
}
Optionalpublic Optional<Date> getUpdated() {
return Optional.ofNullable(obj.getUpdate());
}
3.3.2 How to get value from chained Optional reference path?
Given an object reference path a.getB().getC() with getB() and getC() methods both return Optional objects, we can use flatMap() method to get the actual value. In Listing 5.13, we use flatMap() to get value of reference path a.b.c.value.
Optional reference pathpackage optional;
import java.util.Optional;
public class OptionalReferencePath {
public static void main(String[] args) {
new OptionalReferencePath().test();
}
public void test() {
A a = new A();
String value = a.getB()
.flatMap(B::getC)
.flatMap(C::getValue)
.orElse("Default");
System.out.println(value);
}
static class C {
private final String value = "Hello";
public Optional<String> getValue() {
return Optional.ofNullable(value);
}
}
static class B {
private final C c = new C();
public Optional<C> getC() {
return Optional.ofNullable(c);
}
}
static class A {
private final B b = new B();
public Optional<B> getB() {
return Optional.ofNullable(b);
}
}
}
3.3.3 How to get first value of a list of Optionals?
Given a list of Optional objects, we can use code in Listing 5.14 to get the first value.
Optionalspackage optional;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
public class ListOfOptionals {
public static void main(String[] args) {
List<Optional<String>> optionalList = Arrays.asList(
Optional.empty(),
Optional.of("hello"),
Optional.ofNullable(null),
Optional.of("world")
);
String value = optionalList.stream()
.filter(Optional::isPresent)
.map(Optional::get)
.findFirst()
.orElse(null);
System.out.println(value);
}
}
Starting from Java 9, you can use optionalList.stream().flatMap(Optional::stream) to convert an object of type Stream<Optional<T>> to type Stream<T>.
3.3.4 How to chain method invocations with return value of Optional objects in sequence?
Suppose we want to retrieve a value to be used in the client code and we have different types of retrievers to use, the code logic is that we try the first retriever firstly, then the second retriever and so on, until a value is retrieved. In Listing 5.15, ValueRetriever is the interface of retrievers with only one method retrieve() which returns an Optional<Value> object. Classes ValueRetriever1, ValueRetriever2 and ValueRetriever3 are different implementations of the interface ValueRetriever.
Listing 5.15 shows two different ways to chain method invocations. The first method retrieveResultOrElseGet() uses Optional’s orElseGet() method to invoke a Supplier function to retrieve the value. The second method retrieveResultStream() uses Stream.of() to create a stream of Supplier<Optional<Value>> objects, then filters the stream to only include Optionals with values, then gets the first value.
Optional objects in sequencepackage optional;
import java.util.Optional;
import java.util.function.Supplier;
import java.util.stream.Stream;
public class ChainedOptionals {
private final ValueRetriever retriever1 = new ValueRetriever1();
private final ValueRetriever retriever2 = new ValueRetriever2();
private final ValueRetriever retriever3 = new ValueRetriever3();
public Value retrieveResultOrElseGet() {
return retriever1.retrieve()
.orElseGet(() -> retriever2.retrieve()
.orElseGet(() -> retriever3.retrieve().orElse(null)));
}
public Value retrieveResultStream() {
return Stream.<Supplier<Optional<Value>>>of(
retriever1::retrieve,
retriever2::retrieve,
retriever3::retrieve)
.map(Supplier::get)
.filter(Optional::isPresent)
.map(Optional::get)
.findFirst()
.orElse(null);
}
public static void main(String[] args) {
ChainedOptionals chainedOptionals = new ChainedOptionals();
Value value = chainedOptionals.retrieveResultOrElseGet();
System.out.println(value);
value = chainedOptionals.retrieveResultStream();
System.out.println(value);
}
static class Value {
private final String value;
public Value(String value) {
this.value = value;
}
@Override
public String toString() {
return String.format("Value -> [%s]", this.value);
}
}
interface ValueRetriever {
Optional<Value> retrieve();
}
static class ValueRetriever1 implements ValueRetriever {
@Override
public Optional<Value> retrieve() {
return Optional.empty();
}
}
static class ValueRetriever2 implements ValueRetriever {
@Override
public Optional<Value> retrieve() {
return Optional.of(new Value("hello"));
}
}
static class ValueRetriever3 implements ValueRetriever {
@Override
public Optional<Value> retrieve() {
return Optional.of(new Value("world"));
}
}
}
The output of the program is two lines of Value -> [hello], because ValueRetriever2 is the first retriever that returns a value.
3.3.5 How to convert an Optional object to a stream?
Sometimes we may want to convert an Optional object to a stream to work with flatMap. optionalToStream method in Listing 5.16 converts an Optional object to a stream with a single element or an empty stream.
Optional object to a streampublic <T> Stream<T> optionalToStream(Optional<T> opt) {
return opt.isPresent() ? Stream.of(opt.get()) : Stream.empty();
}
3.3.6 How to use Optional to check for null and assign default values?
It’s a common case to check if a value is null and assign a default value to it if it’s null. Optional can be used to write elegant code in this case. Listing 5.17 shows the traditional way to check for null which uses four lines of code.
nullpublic void test() {
MyObject myObject = calculate();
if (myObject == null) {
myObject = new MyObject();
}
}
Listing 5.18 shows how to use Optional to do the same thing as Listing 5.17with only one line of code.
Optional to simplify codepublic void test() {
MyObject myObject = Optional.ofNullable(calculate()).orElse(new MyObject());
}
3.4 Updates after Java 8
After Java 8 was released, new methods have been added to Optional.
isEmpty() method, introduced in Java 11, is the opposite of isPresent().
ifPresentOrElse(Consumer<? super T> action, Runnable emptyAction) method, introduced in Java 9, performs the given action when the value is present, otherwise performs the emptyAction.
or(Supplier<? extends Optional<? extends T>> supplier) method, introduced in Java 9, returns the current Optional object if the value is present, otherwise invokes the supplier function to return the Optional object.