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

Marco Massenzio
5 min readNov 8, 2016

--

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. And yet, it could be better.

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

All the code mentioned below is publicly available in a Github repository

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, otherwise nothing happens; 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 NPEs or 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.

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).

OptionalFunc<String> opt = ...;opt.ifPresent(System.out::print)
.ifPresent(s -> {
System.out.println(" -- We all love our " +
s.substring(9, 18));
// We can also cause side-effects; but that is not very
// functional and is discouraged.
sb.append("(shhhh... we did something naughty here)");
})
// Here we could chain more calls (to map(), filter(),
// ifPresent() etc.), or just wrap up with this:
.ifPresentOrElse(s -> {
System.out.println("Goodbye and goodnight to our " +
s.substring(19));
}, () -> {
System.out.println("Dammit, nothing to do here!");
return null;
});

will emit something along the lines of:

// Invoked with OptionalFunc.of("Not your grandma's Optional"):
Do something here with something
Not your grandma's Optional -- We all love our grandma's
Goodbye and goodnight to our Optional
[sb]: (shhhh... we did something naughty here)
// Invoked with OptionalFunc.empty():
Dammit, nothing to do here!
[sb]:

Originally published at codetrips.com on November 8, 2016.

--

--

No responses yet