A better mousetrap (or, how to improve on Java 8 Optional)
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 Optional
s:
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
- The use of a
Supplier
whose “supplied” value will be ignored (hence the use ofVoid
) is far from ideal: unfortunately, thejava.functional
package does not define aFunction
which takes no arguments and returns no results.
We presume that the designers of the package assumed people would just use aRunnable
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 ajava.util.concurrent.Callable
would be, if possible, even worse.
So, just add areturn null;
as the last line of your lambda (or method call) and be done with it. - It is worth noting that any mutation to the wrapped
value
that may happen inside the second form ofifPresent()
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.