What is Optional?
The Optional parameter is used to carry objects and enable null references to be handled by various APIs. Let's look at the code snippet:Coffee coffee = new Coffee();
Integer quantity = coffee.getSugar().getQuantity();
We have a Coffee instance in which we get some sugar from an instance of the Sugar object . If we assume that the quantity value was never set in the Coffee constructor , then coffee.getSugar().getQuantity() will return a NullPointerException . Of course, we can always use good old null checks to fix the problem.
Coffee coffee = new Coffee();
Integer quantity = 0;
if (coffee.getSugar() != null) {
quantity = coffee.getSugar().getQuantity();
}
Now everything seems to be fine. But when writing Java code, we'd better avoid implementing null checks . Let's see how this can be done using Optional.
How to create Optional
There are three ways to create Optional objects:-
of(T value) — instantiation of an Optional non-null object. Be aware that using of() to refer to a null object will throw a NullPointerException .
-
ofNullable(T value) - creates an Optional value for an object that can be null.
-
empty() - Creates an Optional instance that represents a reference to null .
// пример использования Optional.of(T Value)
String name = "foo";
Optional<String> stringExample = Optional.of(name)
// пример использования Optional.ofNullable(T Value)
Integer age = null;
Optional<Integer> integerExample= Optional.ofNullable(age)
// пример использования Optional.empty()
Optional<Object> emptyExample = Optional.empty();
So you have an Optional object. Now let's take a look at the two main methods for Optional:
-
isPresent() - This method tells you whether the Optional object contains a non-null value.
-
get() - Retrieves the value for Optional with the current value. Be aware that calling get() on an empty Optional will result in a NullPointerException .
Improving Null Checking with Optional
So how can we improve the above code? With Optional we can understand the presence of an object using isPresent() and retrieve it using get() . Let's start by packaging the result of coffee.getSugar() with Optional and using the isPresent() method . This will help us determine if getSugar() returns null.Coffee coffee = new Coffee();
Optional<String> sugar = Optional.ofNullable(coffee.getSugar());
int quantity = 0;
if (sugar.isPresent()) {
Sugar sugar = sugar.get();
int quantity = sugar.getQuantity();
}
Looking at this example, packaging the result of coffee.getSugar() into Optional doesn't seem to add any value, but rather adds hassle. We can improve the result by using what I consider to be my favorite functions from the Optional class:
-
map(Function<? super T,? extends U> mapper) - Maps the value contained in Optional to the provided function. If the Optional parameter is empty, then map() will return Optional.empty() .
-
orElse(T other) is a “special” version of the get() method . It can get the value contained in Optional. However, in the case of an empty Optional, this will return the value passed to the orElse() method .
Coffee coffee = new Coffee();
Integer quantity = Optional.ofNullable(coffee.getSugar())
.map(it -> it.getQuantity())
.orElse(0);
This is really cool - at least I think so. Now, if in case of empty value we do not want to return the default value, then we need to throw some kind of exception. orElseThrow(Supplier<? extends X> exceptionSupplier) returns the value contained in the Optional parameters, or throws an exception if the Optional is empty.
Coffee coffee = new Coffee();
Integer quantity = Optional.ofNullable(coffee.getSugar())
.map(it -> it.getQuantity())
.orElseThrow(IllegalArgumentException::new);
As you can see, Optional provides several advantages:
- abstracts null checks
- provides an API for handling null objects
- allows the declarative approach to express what is being achieved
How to become effective with Optional
In my work, I use Optional as a return type when a method can return a “no result” state. I usually use it when defining return types for methods.Optional<Coffee> findByName(String name) {
...
}
Sometimes this is not necessary. For example, if I have a method that returns an int , such as getQuantity() in the Sugar class , then the method might return 0 if the result is null to represent “no quantity”. Now, knowing this, we can think that the Sugar parameter in the Coffee class can be represented as Optional. At first glance, this seems like a good idea since, in theory, sugar doesn't need to be present in coffee. However, this is where I would like to address when not to use Optional. We should avoid using Optional in the following scenarios:
-
As parameter types for POJOs , such as DTOs . Optionals are not serializable, so using them in a POJO makes the object unserializable.
-
As a method argument. If a method argument can be null , then from a pure code perspective, passing null is still preferable to passing Optional. Additionally, you can create overloaded methods to abstractly handle the absence of a null method argument.
-
To represent a Collection object that is missing. Collections can be empty, so an empty Collection , like an empty Set or List , must be used to represent a Collection without values.
GO TO FULL VERSION