2020-03-07

Java - Optional Classes

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 deal 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.

public void myMethod(Object val) {
if (val == null) {
throw new IllegalArgumentException("val cannot be null!");
}
// Use of val
}

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
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().

Create Optional objects

Optional<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.

Usage of Optional

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.

public void myMethod(Optional<Object> holder) {
if (!holder.isPresent()) {
throw new IllegalArgumentException("val cannot be null!");
}
Object val = holder.get();
}

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.

Example of Optional.ifPresent

public 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.

Example of Optional.orElse

public 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 invokes getNextAvailablePort()
method to get the port if no value is present.

Example of Optional.orElseGet

public 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.

Example of Optional.orElseThrow

public void anotherMethod(Optional<Object> opt) {
Object val = opt.orElseThrow(IllegalArgumentException::new);
}


Functional usage:

As Optional objects are the holders of actual objects, it’s not convenient 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 a Optional object’s value using
specified predicate. If the Optional object holds a value and the value matches specified predicate,
an Optional object with the value is returned, otherwise an empty Optional object is returned.

Example of Optional.filter

Optional<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 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.

Example of Optional.map

Optional<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 value
when value if present in both current Optional object and mapping result 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.9 use 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.

Example of Optional.flatMap

Optional<Customer> opt = getCustomer();
Optional<ZipCode> zipCodeOptional = opt.flatMap(Customer::getAddress)
.flatMap(Address::getZipCode);
zipCodeOptional.ifPresent(System.out::println);

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 and unwrap an Optional object.

Old legacy library code
public Date getUpdated() {
//Get date from somewhere
}
public void setUpdated(Date date) {
}

Wrap legacy library code with Optional
public Optional<Date> getUpdated() {
return Optional.ofNullable(obj.getUpdate());
}
public void setUpdated(Optional<Date> date) {
obj.setUpdated(date.get());
obj.setUpdated(date.orElse(new Date()));
}

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.12, we use
flatMap() to get value of reference path a.b.c.value.

Get value from chained Optional reference path

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);
}
class C {
private String value = "Hello";
public Optional<String> getValue() {
return Optional.ofNullable(value);
}
}
class B {
private C c = new C();
public Optional<C> getC() {
return Optional.ofNullable(c);
}
}
class A {
private B b = new B();
public Optional<B> getB() {
return Optional.ofNullable(b);
}
}
}


How to get first value of a list of Optionals?

Given a list of Optional objects, we can use code.
Get first value of a list of Optionals
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);
}
}


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 first, then the second retriever and
so on, until a value is retrieved. 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 interface ValueRetriever.

shows two different ways to chain method invocations.
First method retrieveResultOrElseGet() uses Optional’s orElseGet() method to invoke a Supplier function to retrieve the value. 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.
Chain method invocations with return value of Optional objects in sequence
public class ChainedOptionals {
private ValueRetriever retriever1 = new ValueRetriever1();
private ValueRetriever retriever2 = new ValueRetriever2();
private ValueRetriever retriever3 = new ValueRetriever3();
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);
}
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);
}
class Value {
private 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();
}
class ValueRetriever1 implements ValueRetriever {
@Override
public Optional<Value> retrieve() {
return Optional.empty();
}
}
class ValueRetriever2 implements ValueRetriever {
@Override
public Optional<Value> retrieve() {
return Optional.of(new Value("hello"));
}
}
class ValueRetriever3 implements ValueRetriever {
@Override
public Optional<Value> retrieve() {
return Optional.of(new Value("world"));
}
}
}

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 converts an Optional object to a stream with a single
element or an empty stream.
Convert an Optional object to a stream
public <T> Stream<T> optionalToStream(Optional<T> opt) {
return opt.isPresent() ? Stream.of(opt.get()) : Stream.empty();
}

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 default value to it if it’s null. Optional can
be used to write elegant code in this case. Listing 5.16 shows the traditional way to check for null
which uses four lines of code.

Traditional way to check for null
public void test() {
MyObject myObject = calculate();
if (myObject == null) {
myObject = new MyObject();
}
}

Use Optional to simplify code
public void test() {
MyObject myObject = Optional.ofNullable(calculate()).orElse(new MyObject());
}


No comments:

Post a Comment