One of the most common exceptions in Java is the NullPointerException
– simply put this means that somewhere in your program a method is being called on a null
object.
This can be very frustrating and leads to cluttering the code with overly defensive null
checks. This frustration has lead to some languages, like JVM-compatible Kotlin, to be created to solve this issue statically by outlawing null
unless explicitly allowed in the types.
How do we reduce these NullPointerExceptions
in Java, where we don’t have language-level support like Kotlin? We can use a wrapper class introduced in Java 8, called Optional
.
Optional
Very simply put, Optional
is a thin wrapper around another object. The optional instance is either present (and non-null), or empty.
However, this doesn’t give us too many benefits since it is just as safe as checking against null
. The power with Optional
is the many utility methods that is exposes to us:
Optional
methods
You can break down the Optional methods into a few overarching categories – creating an Optional, checking if the Optional is present, “unboxing” the Optional, and interacting/transforming the Optional without unboxing it.
Creating an Optional
There are three ways to create an Optional
object – Optional.empty()
, Optional.of(value)
, and Optional.ofNullable(value)
.
Typically if you’re converting a value to optional, use Optional.ofNullable
. If you’re returning a known value in a method, use Optional.of
or Optional.empty
.
Check if the Optional has a value
There are two methods that return whether the optional variable has a value, isEmpty()
and isPresent()
. They both return a boolean and are opposites of each other.
“Unboxing” the Optional
There are a few ways to “unbox” the value – that is, convert between the Optional
type and the underlying type.
.get()
The first and simplest is get
. This returns the value inside the optional if it exists, or throws an exception if it’s missing. Warning, this (intentionally) leads to runtime exceptions, so this might not be what you want!
.orElse(defaultValue)
A much safer alternative to get
is orElse()
, which accepts a default value to return if the value is missing. This then guarantees that the unboxed value is present, and not null.
.orElseGet(() -> defaultValue)
orElseGet
functions exactly the same as orElse
, but takes a function to run to get the default value instead of the value itself. This is useful to avoid running the default function unless it’s needed.
.orElseThrow(() -> new CustomException())
get()
throws an exception by default if the value is missing. However, it may be useful to throw an exception of a particular type.
Interacting with the Optional without unboxing
Those unboxing methods are useful, but as soon as we unbox the value to its underlying type we lose null safety. Ideally we can keep these values wrapped in an Optional
until we get the final value. Optional
gives us a few methods to help:
.ifPresent(item -> use(item))
This method will call the lambda given with the unboxed value if it exists, or else skip the method. This is equivalent to calling if (item.isPresent())
first, but more concise.
.map(item -> transform(item))
Map lets you transform the value to a new value. This works exactly like .map
in other languages, except it only maps if the value is present. This is chainable!
.filter(item -> conditional(item))
Filter also works the same as in other languages. If the given lambda returns true, then the filter expression returns an Optional
of the original value. If it returns false, then it will be an empty Optional
. Once again, this can be chained!
Alternative way to pass Functions
One note for all the Optional
methods that accept functions, like map
, isPresent
, filter
, etc – those can also accept Method References. Method references look like double colons (::
). This can reduce some of the boilerplate and increase readability.
Putting it all together
In summary, Java’s Optional
class gives us a lot of tools to help avoid unnecessary NullPointerExceptions
in our code. We can push some of the null safety into the type system itself without needing to use a different language like Kotlin. In some cases, this can make the code more concise, too!
This post only detailed a few of the most common and useful Optional
methods. For the full list, the Java docs are a useful reference.