Optional — when `null` is returned
Java programmers tend to perform null checks when they are not always required. Performing null check on a method return value is one example. This tendency is a result of lack of support from Java compiler or Java runtime. That is, Java does not and cannot guarantee that a given reference is always a non-null value.
Java is trying to reduce the number of unnecessary null checks with the introduction of Optional. This article talks about different Optional APIs and how they can be used to handle the null values gracefully.
Using A Default Value
If a method returns null
then use a fall back value.
Without Optional
Restaurant best = findTheBestRestaurant(myPreference);
if(best == null) {
best = this.fallbackRestaurant;
}
best.bookATable();
With Optional — using orElse
Optional.ofNullable(findTheBestRestaurant(myPreference))
.orElse(this.fallbackRestaurant)
.bookATable();
Throwing An Exception
In some cases, a null
returned value is an exceptional case, and we want to bubble up the exception.
Without Optional
Restaurant best = findTheBestRestaurant(myPreference);
if(best == null) {
throw new NoDataFoundException("none found");
}
best.bookATable();
With Optional — using orElseThrow
Optional.ofNullable(findTheBestRestaurant(myPreference))
.orElseThrow(() -> new NoDataFoundException("none found"))
.bookATable();
Calling Another Method As Fallback
And finally, in some cases, we might want to call another method to get the second-best choice.
Without Optional
Restaurant best = findTheBestRestaurant(myPreference);
if(best == null) {
// do not worry about the preference
best = findTheBestRestaurant();
}
best.bookATable();
With Optional — using orElseGet
Optional.ofNullable(findTheBestRestaurant(myPreference))
.orElseGet(() -> findTheBestRestaurant())
.bookATable();
Why Is The Code With Optional Better?
- Optional makes code more readable because it avoids the
if-else
blocks. It becomes significantly better when you have null checks in the nestedif-else
block. - Having fewer blocks means fewer exit points which means more maintainable code.
- The code feels more natural (once you are familiar with the APIs).
- Reading a value in Optional opens up a rich set of APIs to further manipulate the read value. For example:
Optional.ofNullable(findBestRestaurant())
.filter(r -> r.hasVeganOptions() && r.isOpen())
.map(r -> r.getFreeTable())
.ifPresent(table -> table.book());
As compared to
Restaurant r = findBestRestaurant();
if(r != null) {
if(r.hasVeganOptions() && r.isOpen()) {
Table table = r.getFreeTable();
if(table != null) {
table.book();
}
}
}
One Last Thing
It is important to note the difference between orElse() and orElseGet(). Either of them can be used in the following scenario but one of them is more efficient.
Option 1
Optional.ofNullable(findTheBestRestaurant(myPreference))
.orElse(findTheBestRestaurant())
.bookATable();
Option 2
Optional.ofNullable(findTheBestRestaurant(myPreference))
.orElseGet(() -> findTheBestRestaurant())
.bookATable();
Option 2 is more efficient because findBestRestaurant()
is called lazily (that is, only if Optional is wrapping a null
value) whereas in Option 1 findBestRestaurant()
is always called whether the Optional is wrapping a null
value or not.