What’s the fuss with Java 8 Optional?

April 15, 2015 by Michael

I’ve been asked several times lately by colleagues what the Optional<T> is good for, so today some more basic stuff.

You can use the Optional class is as an elaborate null check like

  Optional<String> optionalString = Optional.ofNullable(someString);
  if(optionalString.isPresent()) {
    String string = optionalString.get();
  }
 
  // Or worse
  // String string = optionalString.orElse(null);
  // null check here

but most of the time, a computed value based on a possible null input value is much more interesting. Here comes map and flatMap.

If the optional value is present, map is used to compute a new value (which is turned into an optional), flatMap has the same purpose but is meant for computations that already return an Optional, otherwise you would end up with an Optional<Optional<T>>:

// Compute the length of a string which could be null
int length = Optional.ofNullable(someString).map(String::length).orElse(0); // Which value is used as default is surely debatable

Here’s a real world example from biking. Spring framework supports Optional request parameters and path variables since 4.1, so i can write a controller method that takes two optional range variables (start and end) like this:

I can use these two parameters to filter a map containing LocalDates as keys without null checks like so:

bikes
    // Stream the bikes 
    .stream()
    // and flatMap (concat) their periods into a new stream
    .flatMap(bike -> bike.getPeriods().entrySet().stream())
    // we're only interested in periods before 1.1 of the current year
    .filter(entry -> entry.getKey().isBefore(january1st))
    .filter(entry -> {
	final int year = entry.getKey().getYear();	    		
        // No need for null checks, the optional parameters are mapped to booleans
        // if the values are present, check if the current year is after start respectivly before end
        // otherwise the interval is open and i can map them to true
	return yearStart.map(v -> v <= year).orElse(true) && yearEnd.map(v -> year < v).orElse(true);
 
        // Using possible null values the the filter would look like
        // (yearStart == null || (yearStart <= year)) && (yearEnd == null || (year > yearEnd))
    })

Now, it’s actually debatable which expression brings the intention more clearly, but i like the fact that i just can use the parameters as is.

Another interesting Optional method is filter. It takes a predicate and turns a non-empty optional into an empty optional if the predicate doesn’t match. I use it for example for an “default if blank string method”:

/**
 * Returns {@code defaultValue} when {@code value} is empty or blank.
 * 
 * @param value A string
 * @param defaultValue A default value when the input string is empty.
 * @return value or defaultValue
 */
public static String defaultIfBlank(final String value, final String defaultValue) {
    return Optional.ofNullable(value).filter(Strings::isNotEmpty).orElse(defaultValue);
}
 
/**
 * @param value Value to be checked if empty
 * @return True if trimmed value is not empty
 */
public static boolean isNotEmpty(final String value) {
    return !value.trim().isEmpty();
}

Do you have interesting use cases for Java 8s Optional?

2 comments

  1. I was impressed with Julian Dubois implementation of AccountResource#getAccount() which makes use of Optional.ofNullable() for a conditional ResponseEntity … struck me a very cool…

    Posted on May 25, 2015 at 5:34 AM | Permalink
  2. Michael wrote:

    Hi Edward, yeah, that’s a nice example as well which i’m using too at several places (I’m returning a 404, though).

    That was is also handy, applies to Spring Data JPA Repositories which can return an optional for finders, which need just to be declared:

    public Optional<SensorEntity> findByName(final String name);

    And then find an entity by its unique name, create and return a new one if no such entity was present:

    final SensorEntity sensorEntity = sensorRepository
    		.findByName(name)
    		.orElseGet(() -> sensorRepository.save(new SensorEntity(name, "CCCCCC")));
    Posted on May 25, 2015 at 7:44 AM | Permalink
Post a Comment

Your email is never published nor shared. Required fields are marked *