A better mousetrap (or, how to improve on Java 8 Optional)

Overview

The java.util.Optional class is a great addition in Java 8 as it enables a more expressive way of conveying the concept of a library API return value that may not be there.

While it is not meant to replace the use of null values in code (and you will get warnings not to use Optional variables in your code) it really is useful in, for example, returning values from queries to remote APIs, databases, etc.

Additionally, it also exposes a functional interface, encouraging a more “functional way” of dealing with potentially missing values (the API closely mirrors the Stream API introduced also in Java 8 Collections).

Improvements

One of the most interesting methods introduced by the Optional type is ifPresent() which is defined as follows:

     public void ifPresent(Consumer<? super T> consumer);

The contract of this method is that, if there is a value wrapped by this Optional the consumer will be invoked, with the value provided as its sole argument; in practice, this is how one uses it:

Optional<Foobar> myResult = getMeSomeFoo(...);
myResult.ifPresent(foo -> {
    LOG.info("Got me some foo: {}", foo.getName());
    // do something with foo
    // ...
});

which is pretty awesome, as one can do entirely away with checking for null and all the other hoops one has to jump through when dealing with a maybeValue:

Person user = dbConn.findById(id);
if (user != null) {
  // do something with `user`
  // ...
} else {
  LOG.warn("User [{}] not found", id);
  // Much unhappines ensues...
}

With Optional, if you do need to take some action when the value is missing you are firmly back in if-then-else land:

Optional<Foobar> myResult = getMeSomeFoo(...);
if (myResult.isPresent() {
    Foobar foo = myResult.get();
    LOG.info("Got me some foo: {}", foo.getName());
    // do something with foo
    // ...
} else {
    LOG.warn("Dammit, no foo here!");
    // do something else to express your displeasure
    // ...
}

It’s not exactly horrible, but an entirely different approach from the more elegant approach of the getOrElse() way of dealing with Optionals:

Foobar myFoo = getMeSomeFoo(...).getOrElse(defaultFooValue);

Wouldn’t it be nice if we could do that with ifPresent() too?

Also, note that the other “functional interfaces” (map() and filter()) allow one to “chain” calls, which will just come to nought if one of the Optionals is missing along the way:

    // This will map all foos into people and print those
    // of legal age to drink...
    Optional<Foobar> maybeFoo = ...;
    maybeFoo.map(transformer::transform)
        .filter(v -> v.getAge() > 21)
        .ifPresent(Store::buyMargaritas);

If, for whatever reason, we can’t transform maybeFoo into a Person type, or it turns out to be younger than 21, no harm done, nothing happens (and, look ma’, no exception handling!).

Unfortunately, again, as the return type of ifPresent() is void, it must be the last call in the chain, and there is no action we can take, if it turns out to be that maybeFoo is a minor (what if we want to alert her parents that she tried to buy a round of Margaritas?).

Solution

Our solution is to augment the Optional class with two methods that present a more “functional” interface (hence the OptionalFunc name) and would allow one to overcome the issue mentioned above:

public void ifPresentOrElse(Consumer<? super T> consumer, 
                            Supplier<Void> orElse);

public OptionalFunc<T> ifPresent(Consumer<? super T> consumer);

The first one takes an additional Supplier (see also below) that will be invoked if there is no value in the wrapped Optional; the second one will just return the wrapped value back to the caller (by then, it’s obvious that it’s empty, but it allows then us to insert the call in a chain, without necessarily this being the last in the chain.

Commentary

  1. The use of a Supplier whose “supplied” value will be ignored (hence the use of Void) is far from ideal: unfortunately, the java.functional package does not define a Function which takes no arguments and returns no results.

    We presume that the designers of the package assumed people would just use a Runnable and be done with it, but this looks awkward to me (not to mention, in people’s minds, Runnable is very closely associated with threading and using it would give the false impression to the occasional user that that method may be run in a background thread – which it most definitely it isn’t).

    And using a java.util.concurrent.Callable would be, if possible, even worse.

    So, just add a return null; as the last line of your lambda (or method call) and be done with it.

  2. It is worth noting that any mutation to the wrapped value that may happen inside the second form of ifPresent() may be reflected further along the chain: as a “mutating” call with side-effects, this is not very “functional” (always prefer immutability): if you use it, please avoid mutating the inner value; if you do, make sure you make it very clear in the comments.

  3. As pictures are worth a thousand words, and example code a thousand README’s, please see the example code in the Main class for some examples of how this can be used to replace the “plain vanilla” Optional (see also the Javadoc comments).

Advertisement

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: