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 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 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 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. -
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).
Leave a Reply