Use Java8 Optional to Avoid Problems

Use Java 8 Optional to avoid unexpected conditions like NPE in your code.

“Life always begins with one step outside of your comfort zone.”
― Shannon L. Alder

1. Introduction

Java version 1.8 provides the new class Optional which represents an abstration of an optional value. That is the object may contain a value or a null. This class provides several useful features which help java programmers avoid NullPointerException. Let us look into this class in more detail.

2. Motivation for an Optional

When a method is required to return a value, it may happen that there is no result to return. For example, a search for a sub-string in a string fails and method which needs to return the position of the sub-string has nothing to return. In these cases, it was previously typical to return a normally impossible value such as -1 or null.

private int search(String text,String substr)
{
  // .. perform search here
  // search failed. return -1
  return -1
}

The client code would use this function and check for the return value as follows.

if ( search(text, substr) != -1 ) {
    // found match
}

Such coding requires an impossible value such as -1 above. However, using java 8, it is possible to return an OptionalInt instead, and not rely on any such value.

(An OptionalInt is specialized for using a primitive int instead of having to use a boxed Integer with Optional.)

private OptionalInt search(String text,String substr)
{
    if ( ... ) {
        // match found, return position "index".
        return OptionalInt.of(index);
    } else {
        return OptionalInt.empty();
    }
}

3. Using the Optional

And how can one use such a returned OptionalInt in client code to check and extract the return value? There are several options here.

  • The simplest is to use isPresent() to check whether the OptionalInt contains a value and use getAsInt() subsequently.
    OptionalInt opt = search(text, substr);
    if ( opt.isPresent() ) {
      int index = opt.getAsInt();
      // use the index here.
      System.out.println("Found match at: " + index);
    }
    
  • Another method is to specify a lambda function to be executed with the return value, if it exists.
    opt.ifPresent(index -> {
          System.out.println("Found match at: " + index);
      });
    
  • If you have a valid default value to be used in case the OptionalInt is empty, you can use orElse(). In the following code snippet, index will contain the returned value if present, or 0.
    int index = opt.orElse(0);
    
  • Do you need to compute a default value in case the call failed? Use orElseGet(). The following returns the length of the string when the OptionalInt value is not present.
    int index = opt.orElseGet(() -> {
          return text.length();
      });
    
  • On the other hand, if you don’t mind handling a NoSuchElementException in case of failure, you can directly use the value. This is far preferable to handling a possible NullPointerException, which might arise due to many causes.
    try {
      System.out.println("Found match: " + opt.getAsInt());
    } catch(NoSucElementException ex) {
      System.out.println("Search failed");
    }
    
  • Would you like to throw your own exception in case the operation fails? This is somewhat equivalent (in a round-about way) to having the method throw the specified exception directly in case of failure.Note that you need to return the exception instance from the lambda function instead of directly attempting to throw it from the lambda.
    int index = opt.orElseThrow(() -> {
          // assuming MyException is defined.
          return new MyException("Operation failed");
      });
    

4. Mapping Optional Types

Sometimes it is useful to map an Optional from one type to another. Maybe you have captured a user’s preference on a config file path in a string. You would like to load the config file if it is specified. The following code opens the config file if it is specified, loads the configuration and closes the file, all in one statement.

Optional<String> cfg = args[index].equals("-config") ?
    Optional<String>.of(args[index+1]) : Optional<String>.empty();
cfg.map(x -> new FileInputStream(x)).ifPresent(f -> {
        loadConfig(f);
        f.close();
    });

Of course, this is a trivial case and you can always use an if condition for this case, but this example illustrates the usage of the map() function.

Conclusion

This article touched on some use cases for java 8 Optional. It is possible to write cleaner code when using Optional for representation as appropriate.

Leave a Reply

Your email address will not be published. Required fields are marked *