You don't need to check its presence

Anatomy of Optional and why you are using it wrong.

Every Java programmer knows Optional type. If someone doesn't, shame on her/him. In this post I'll show you the power of Optional and will explain in details where does this power comes from.

What is Optional?

First of all, Optional is a Monad. There will be more about Monads in another post but for the sake of this article I'm highlighting some characteristics.

  • Every Monad has factory method: Optional.ofNullable() or Optional.of()
  • Every Monad is a parameterized container: Optional<T>
  • Every Monad has a mapping method taking function accepting monad's type and returning Monad: Optional<U> flatMap(Function<? super T, Optional<U>> mapper)
  • Monads are immutable
  • Finally and last but not least Monad is a Monad only if it obeys three laws: Left identity, Right identity, Associativity. You must take it on faith now, Optional obeys all of them.

Optional is a Monad indicating whether it holds some value or is empty. It is very useful replacement of null and we should avoid nulls at all costs because null is a huge code smell. There is not enough place here to express how smelly it is but we can Google it: java null criticism,

Why you are using Optional wrong?

Is it happening to you more often than very rarely to write code like this?

If it does, please stop doing this, you are hurting all Monads. The fact that Optional.isPresent() is available doesn't mean that we should use it except exceptional situations. If we would like to follow monadic conventions, above code should look like this:

These code snippets lengths difference is striking, isn't it? The calculateSalary method signature is changed too, the return type is Optional<BigDecimal> now. It might seem to be minor difference but is quite significant. We don't have to document internal implementation behavior of returning null in case of not found employee, we expressed it by method's signature. We inverted the control making caller's code to decide what to do in such case, whether pass some other Optional further or maybe throw an exception. We just don't care here and this is good.

Don't consume Optional to early, let it flow through your code for the sake of clarity and safety.

Map and filter it

Next case are the heavily underused map and filter methods. Let's say we have string values stored in the map and expose it via some service class. Additional features provided by this class is the validation of the keys used to obtain values and some method used to decorate returned values. This is traditional example implementation.

This series of if statements can be easily replaced with Optional.

We can go further and get rid of blank key check before Optional creation.

We are allowed to use cache::get in line 10 because Optional.map() expects Function<? super String,? extends String> there. It means function accepting any superclass of String and returning any subclass of String. Our cache.get() method reference with signature Function<Object,String> fits there.

I personally find it much more convenient and descriptive to apply filters and transformations in this way. If you don't, maybe next example will be more convincing.

Our task it to write method parsing text provided by user to percent format. To make things more complicated, users are from country using comma as decimal separator. This is simple implementation.

And this is equivalent implementation using Optional.

One might say that this is semantically the same code, only syntax vary. Well, that's true, but there is one difference you might not spot. In first example we did several intermediate assignments in lines 7, 9 and 11 to increase readability. It required additional mental work to think about clean variables names. When we perform series of transformations with Optional.map() we can avoid this job.

Additional thing to point out is the weird exception thrown in line 19. This is dead code actually so it doesn't matter what exception is declared to be thrown, it will never be thrown anyway. It will never be thrown because any problematic value will cause NumberFormatException in line 11. It doesn't look good and would trigger questions and discussions. The reason is impure functions usage and bad API design. The impure function is BigDecimal constructor. It throws NumberFormatException in case of non number string provided as an argument. Pure functions always return value and never throw exceptions. In previous examples we also saw the rule applied: Don't consume Optional to early . Let's change the parser code to return Optional and use pure functions.

This time we use pure createBigDecimal(String str) function to create Optional<BigDecimal> from String. It will return Optional.empty() in case of invalid number representation. To handle nested Optional mapping, we use Optional.flatMap() in line 19, otherwise the type returned by expression would be Optional<Optional<String>>. One thing to notice is that we don't know the reason why our parsePercent function returned empty Optional: because provided argument is null, blank string or invalid number representation? The calling code knows only whether there is some value or there is now value returned. There is handy way of handling such cases with ... of course another Monad named Try. There will be another post about Try Monad but here below is the same code using Try from Vavr library.

The Try and Optional based code similarities are really striking, so much that it's easy to overlook the differences by just reading the code. Furtunately, the compiler will spot the difference. The caller code will not differ much as well. Used Try implementation provides bunch of methods very similar to those supplied by Optional, like: orElse, getOrElseGet or getOrElseThrow. We can even convert the Try to Optional with toJavaOptional() method if we don't care about error details.

This is the true power of Monads: we use all of them in the same way. If we know how to use Optional, we know how to use other Monads.

Handling missing nested values

To let you feel what I mean by deep nested missing values, I'd like to recall you old Java XML W3C DOM API. I find it most tedious to work with, because methods it provides, very often return null values. Additionally, XML's Document Object Model is the tree of nodes that can be really deep.

Let's imagine that we have users database stored in XML file like this one:

As we can see, some users have address specified and some do not. To get user's street from address, we have to access it via quite long path:

  • find node named user
  • check whether child node id has expected text content
  • get location child node
  • get address child node if location is present
  • get street child node if address is present

I'll let you you imagine how boring and error prone native DOM API use would be. To mitigate this, we created several functions operating on org.w3c.dom.Node and using Optional as return value heavily.

First, we need the easy way to iterate over child nodes. Unfortunately, NodeList returned by Node.getChildNodes() isn't Iterable. Lets' create our own implementation.

If we have Iterator, we can use Stream created from it. This is function creating Stream from NodeList.

Next, we need some predicates to check various node properties.

As we can see, there is Optional used all the time. This is due to the fact, that Node methods can return null depending on Node type. For example, getNodeName() or getAttributes() methods called on text node always return null. We create Optional using of() factory method because we don't accept null in predicates. In this way, we will fail fast with NullPointerException.

Then we need functions finding nodes. Here they are.

Finally, if we have node finding functions, we can easily create additional useful predicate checking whether given Node has child matching given condition.

Let's see our toys in action.

This code is totally safe (unless we left some bugs) and way easier to write and comprehend. We created custom Node Predicate from primitive ones for convenience and used in line 3.

This predicate checks, whether given node name is user and has child node named id with text content equal given id argument.

Below test code is identical to previous on, only assertion differs because we don't expect Mary Jane (having id 2) to have address specified at all.

What if we ask about non existing user? No problem.

There is no user with id 3 and the test doesn't fail.

And what about using attributes not present in our XML file? No issues at all.

There is no user node with attribute name and the test passes.

As we can see, by writing less then 100 lines of code using Optional heavily we made our life with XML DOM API easier and safer.

Eager and lazy alternative value

One of the frequent usages of Optional is to get alternative value in case of empty Optional. We do this via orElse(T other) or orElseGet(Supplier<? extends T> other) method. It's common mistake to use orElse while orElseGet should be used. The difference between these methods is that orElseGet takes Supplier, so actual alternative value is evaluated lazy, only if is needed meaning when Optional is empty. Java evaluates methods arguments before method body execution, we call it eager evaluation. Improper selection of alternative value provider may result in unexpected behaviors, like in below example.

Let's say, we have repository interface and implementation backed up by some persistence technology. We express missing lookup by primary identifier by returning Optional from appropriate method.

getDefault in line 5 returns the Entity by hardcoded id value and can be used if the one we were looked for was not found. It also prints debug message for presentation purpose. Here below is simple test showing the difference between eager and lazy evaluation of Optional alternative value.

orElseGet alternative value provider works in this way because it expects Supplier implementation. Supplier is the functional interface with single abstract method T get(). This is all we need to obtain default value, just call get() and return its result. This is exactly what orElseGet method does. Without calling Supplier.get() nothing happens. On the other side, orElse expects default value which is evaluated before execution of our code using Optional. If we want to return simple default value, like BigDecimal.ZERO, Optional.orElse() is perfectly legitimate.

Whenever additional operations are needed to provide default value in case of empty Optional, use orElseGet. If the default value is just simple object or constant, use orElse.

Anatomy of Optional

If you think that behind Optional's powers stays some sophisticated code ... you are wrong. The concept and implementation is actually very easy. The best way to see what is happening under the hood is to implement our version of Optional. Let's name it Maybe to distinguish it from Java's Optional and Scala's Option.

Our Maybe is the generic container, so it has to be parameterized.

If we think about very nature of Maybe it's just either something or nothing. We can easily design it as abstract class with only two implementations: Just and Nothing. We don't want to allow any other implementations to exist, so let's make Maybe, Just and Nothing constructors private. Just and Nothing will be static and final inner classes of Maybe.

Just should hold some value, let's add the value placeholder to it.

There is only one and only one Nothing, we can create singleton for it.

If you wonder why NOTHING singleton is of unbounded wildcard type, the answer is simple: Nothing holds no value, it's type is meaningless and can be discarded.

Next, we need to have the ways to create Just and Nothing instances. We can't just construct them, because their constructors are private. Let's write appropriate factory methods in Maybe.

By factoring Nothing we always return our singleton instance NOTHING. We do cast it to Maybe<A> to avoid unnecessary unchecked cast warnings. Remember? Nothing holds no value and has no type actually, it's safe to cast it to Maybe of any A. Just on the other hand must hold the value, null is not acceptable, hence we validate just factory method argument against null references in line 7.

We would like to create Maybe without knowing if it's nothing or something too. Let's craft the method doing so.

This is what we have so far.

We may have a value in Just but we don't know how to check it. Let's add Maybe.isPresent() abstract method and implementations. Obviously, implementation will be simple but completely different.

This is Just:

And this is Nothing:

Let's quickly go through value accessors declaration in Maybe, they are not very interesting anyway.

These are Just implementations:

And these are taken from Nothing:

We do completely opposite things in Just and Nothing respectively. This kind of symmetry is often present in functional programming where Monads were born.

Finally the most important methods: flatMap and map. If you wonder why flatMap is in the first place ... the reason is simple, map can be implemented using flatMap.

Let's start with map because it's easier to comprehend. Its signature is as follows: Maybe<B> map(Function<? super A,? extends B> f). f argument is named after Function. What this method does is: it takes given mapping function and returns new Maybe containing result of this function applied to Maybe value . Nothing implementation is simple, there is no value to apply given function to, we have only one choice, return Nothing.

Just.map() implementation is very straightforward too:

flatMap behavior is harder to explain and easier to write: take Maybe with some value in it and apply mapping function returning other Maybe to original Maybe value. Nothing.flatMap() returns Nothing, no surprises here.

Just.flatMap() simply returns the result of given function applied to wrapped value.

flatMap is the feature making Maybe a Monad, actually map is redundant because can be implemented using flatMap. It is usually implemented separately for performance reasons.

mapViaFlatMap is a method implemented in our abstract Maybe class. Presented above code is very functional. We can read is as: take f function, compose it with Maybe::maybe, the composed function is maybe(f(v)) with signature Function<? super A,Maybe<B>>, and pass it to flatMap. We can also implement it using lambda expression if we find it easier to grasp:

If you don't know what function composition is, you may find my post Functional programming magic quite helpful. If you would like to see full Maybe source, click here. You will find side effects performing ifPresentOrElse method there.

That's it, about 100 lines of code and we have fully functional Optional replacement. We reinvented the wheel, there are plenty implementations available but our understanding of Optional is much better thanks to this exercise.

Summary

We know how to use Optional. We know how simple and easy to implement concept it is. This is always like this: great power lies in simplicity. I personally can't imagine my life without some kind of Optional in my programming toolkit and I hope that after reading my post you will not be able to do so either.


We use cookies to ensure you get the best experience on our site.