Friday, September 17, 2021

Java's Optional Does Not Supplant All Traditional if-null-else or if-not-null-else Checks

Java's addition of java.util.Optional has been welcome and had led to more fluent code for methods that cannot always return non-null values. Unfortunately, Optional has been abused and one type of abuse has been overuse. I occasionally have run across code that makes use of Optional when there is no clear advantage over using null directly.

A red flag that can tip off when Optional is being used for no advantage over checking for null directly is when calling code employs Optional.ofNullable(T) against the returned value from the method it has just invoked. As with all "red flags," this doesn't mean it's necessarily a bad thing to pass the returned value from a method to Optional.ofNullable(T) (in fact, it is necessary to pass to APIs expecting Optional), but it is common for this approach to be used to provide no real value over using the returned value directly and checking it for null.

Before Optional was available, code to check for null returned from a method and acting one way for null response and another way for non-null response is shown next (all code snippets in this post are available on GitHub).

/**
 * Demonstrates approach to conditional based on {@code null} or
 * not {@code null} that is traditional pre-{@link Optional} approach.
 */
public void demonstrateWithoutOptional()
{
    final Object returnObject = methodPotentiallyReturningNull();
    if (returnObject == null)
    {
        out.println("The returned Object is null.");
    }
    else
    {
        out.println("The returned object is NOT null: " + returnObject);
        // code processing non-null return object goes here ...
    }
}

For this basic conditional, it's rarely necessary to involve Optional. The next code snippet is representative of the type of code I've occassionally seen when the developer is trying to replace the explicit null detection with use of Optional:

/**
 * Demonstrates using {@link Optional} in exactly the manner {@code null}
 * is often used (conditional on whether the returned value is empty or
 * not versus on whether the returned value is {@code null} or not).
 */
public void demonstrateOptionalUsedLikeNullUsed()
{
    final Optional<Object> optionalReturn
       = Optional.ofNullable(methodPotentiallyReturningNull());
    if (optionalReturn.isEmpty())
    {
        out.println("The returned Object is empty.");
    }
    else
    {
        out.println("The returned Object is NOT empty: " + optionalReturn);
        // code processing non-null return object goes here ...
    }
}

The paradigm in this code is essentially the same as the traditional null-checking code, but uses Optional.isEmpty() to perform the same check. This approach does not add any readability or other advantage but does come at a small cost of an additional object instantiation and method call.

A variation of the above use of Optional is to use its ifPresent(Consumer) method in conjunction with its isEmpty() method to form the same basic logic of doing one thing if the returned value is present and another thing if the returned value is empty. This is demonstrated in the following code.

/**
 * Demonstrates using {@link Optional} methods {@link Optional#ifPresent(Consumer)}
 * and {@link Optional#isEmpty()} in similar manner to traditional condition based
 * on {@code null} or not {@code null}.
 */
public void demonstrateOptionalIfPresentAndIsEmpty()
{
    final Optional<Object> optionalReturn
       = Optional.ofNullable(methodPotentiallyReturningNull());
    optionalReturn.ifPresent(
       (it) -> out.println("The returned Object is NOT empty: " + it));
    if (optionalReturn.isEmpty())
    {
        out.println("The returned object is empty.");
    }
}

This code appears bit shorter than the traditional approach of checking the returned value directly for null, but still comes at the cost of an extra object instantiation and requires two method invocations. Further, it just feels a bit weird to check first if the Optional is present and then immediately follow that with a check if it's empty. Also, if the logic that needed to be performed was more complicated than writing out a message to standard output, this approach becomes less wieldy.

Conclusion

Code that handles a method's return value and needs to do one thing if the returned value is null and do another thing if the returned value is not null will rarely enjoy any advantage of wrapping that returned value in Optional simply to check whether it's present or empty. The wrapping of the method's returned value in an Optional is likely only worth the costs if that Optional is used within fluent chaining or APIs that work with Optional.

6 comments:

robdbirch said...
This comment has been removed by the author.
robdbirch said...

I think this might be at a corner case, maybe dealing with void or using print like statements?

Using Optional methods this example can be succinctly written as:

class NullOrOptional {

Object methodReturningNull() {
return null;
}

Object methodNotReturningNull() {
return new Object();
}
}

final NullOrOptional maybe = new NullOrOptional();

// This is basically the replacement for the if ... then ...
out.println("Verdict: " + Optional.ofNullable(maybe.methodReturningNull())
.map(value -> "Not null: " + value)
.orElse("The returned Object is null"));

Being unfamiliar using the Optional and more familiar with if{} then{} can make the above look initially odd.
It is still probably true that "... Optional Does Not Supplant All Traditional if-null-else or if-not-null-else Checks", but I think it might be a rarity.

Rob McDougall said...

I've seen examples like Rod's in real code:
Optional.ofNullable(someMethod()).orElse(someDefaultValue);

While it's succinct, I think it unnecessarily creates an object when you could instead use the following:
Objects.requireNonNullElse(someMethod(), someDefaultValue);

It pays to watch some of the API changes from one version to the next. There are often little gems like Objects.requireNonNullElse() that may go unnoticed.

Conzo77 said...

I really like Optionals (although Optional.isPresent + Optional.get() give me the creeps, too), and I don't have a problem with the code example

Optional.ofNullable(someMethod()).orElse(someDefaultValue); (whether a solution creates an extra option or not is unimportant for my typical use cases).

However, I agree with Rob McDougall and his recommendation to check API changes. I've gotten a lot of mileage out of Objects.requireNonNullElse(). One of my favorites is:

if (!Objects.requireNonNullElse(someString, "").isEmpty() ) { ... }

Valery said...
This comment has been removed by the author.
Valery said...

Method ifPresentOrElse() was added in Java 9.

Sample Program:

// Java program to demonstrate
// Optional.ifPresentOrElse() method

import java.util.*;

public class GFG {

public static void main(String[] args)
{

// create a Optional
Optional op = Optional.of(9455);

// print value
System.out.println("Optional: " + op);

// apply ifPresentOrElse
op.ifPresentOrElse(
(value) -> { System.out.println("Value is present, its: "+ value); },
()-> { System.out.println("Value is empty"); });}
}


Output:

Optional: Optional[9455]
Value is present, its: 9455