Optional
type(和訳, Japanese translation, Java Magazine version)
JDK 8 introduced the
Optional
class, a container that is either empty or contains a non-null value.
Optional
has numerous problems without countervailing benefits.
Optional
does not make your code more correct or robust.
There is a real problem that Optional
tries to solve, and this
article shows a better way to solve it. Therefore, you are better off
using a regular possibly-null Java reference, rather than using
Optional
.
The Web and blogosphere are full of claims that the Optional
class solves the problem of null pointer exceptions. This is not
true. Changing your code to use the Optional
class has
these effects:
NullPointerException
into a
NoSuchElementException
, which still crashes your program.
When your code throws a NullPointerException
or NoSuchElementException
, the
underlying logic bug is that you forgot to check all possibilities when
processing data. It's best to use a tool that guarantees you don't
forget. That helps you to understand and fix the underlying problem.
(These criticisms aren't specific to Java's implementation of Optional
.
Other languages that claim to have solved the null pointer problem have also merely
transformed it into a different manifestation.)
The Optional
class isn't all bad: if you have to use
Optional
, it defines methods for reducing code clutter when
dealing with possibly-present data. However, you should still avoid the
Optional
class.
The rest of this article expands on the above points. Here is the outline of this article:
Optional
is prone to misuseOptional
clutters your codeOptional
introduces overheadOptional
's handy methods
Consider a Cartesian point class with fields x
and y
(the discussion is equally applicable to getter methods):
class Point { int x; int y; }
Because a Java reference may be null, there is a danger of a
NullPointerException
whenever you dereference it, as in myPoint.x
below:
Point myPoint; ... ... myPoint.x ...
If myPoint
is null and does not refer to a real point, then
myPoint.x
throws a NullPointerException
and the program crashes.
Here is a way to write this code using Optional
:
Point myPoint; Optional<Point> myOPoint = Optional.ofNullable(myPoint); ... ... myOPoint.get().x ...
If myOPoint
does not contain a real point, then myOPoint.get().x
throws a NoSuchElementException
and the program crashes.
This isn't any better than the original code, because the programmer's goal
is to prevent all crashes, not just NullPointerException
crashes!
It is possible to prevent the exception and crash by doing a check first:
if (myPoint != null) { ... myPoint.x ... }
or
if (myOPoint.isPresent()) { ... myOPoint.get().x ... }
Again, the code is very similar and Optional
is not superior to using a
regular Java reference.
Optional
is prone to misuse
Optional
is a Java class, and therefore the variable myOPoint
of type
Optional<Point>
might be null. Thus, the expression myOPoint.get()
might
throw a NullPointerException
or a NoSuchElementException
! You really
need to write
if (myOPoint != null && myOPoint.isPresent()) { ... myOPoint.get().x ... }
You can express complex data using the distinction between a null Optional
value, a non-null Optional
with data absent, and a non-null Optional
with
data present, but this is complex and confusing.
Alternately, you could decide to forgo those possibilities, and to be
careful and disciplined about not letting variables of type Optional
be
null. However, if you trusted yourself about that you wouldn't have had
any null pointer exceptions in the first place and you wouldn't be using
Optional
.
Optional
is a wrapper, so uses of value-sensitive operations are
error-prone, including reference equality (==
), identity hash codes, and synchronization. You need to remember not to use these.
Stuart Marks gives a longer list of
rules
to avoid mistakes in the use of Optional
.
Optional
clutters your code
With the Optional
library, your code is more verbose:
Optional<Point>
vs. Point
myOPoint.isPresent()
vs. myPoint == null
myOPoint.get().x
vs. myPoint.x
No one of these is a deal-breaker, but overall it is cumbersome and ugly to use.
For some concrete examples, see http://www.codeproject.com/Articles/787668/Why-We-Should-Love-null and search for the word “cumbersome”.
Optional
introduces overhead
Optional
introduces space overhead: an Optional
is a separate object that
consumes extra memory.
Optional
introduces time overhead: Its data must be accessed via an extra
indirection, and calling methods on it is more expensive than Java's
efficient test for null.
Optional
introduces coding overhead: You have to deal with
its incompatibility with existing interfaces that use null
,
with the fact that it is not serializable, etc.
A NullPointerException
or NoSuchElementException
occurs because the programmer forgot to perform a check that data is present, via != null
or .isPresent()
, before trying to use the data.
Many people say that the main benefit of Optional
is that with
Optional
, you are less likely to forget to perform the check.
If true, that is good! Nonetheless, it's not enough to make a problem
somewhat less likely, in a few places where Optional
was
written. It is better to have a guarantee that eliminates the problem
everywhere.
One way would be to force the programmer to always perform the check before accessing the data. (This is what some programming languages do, by offering a destructuring or pattern-match operator.) This would result in many redundant checks in places where the check has already been performed or it is not really needed. (As an analogy, think about how some programmers react to checked exceptions, which force the programmer to do a check whether the programmer wants to do it or not.)
A better approach is to have a tool that guarantees that you do not forget to check, but that also doesn't require redundant checks. Luckily, such tools exist! Examples include the Nullness Checker of the Checker Framework, NullAway, and Infer.
As an example, consider
the Nullness
Checker.
It works at compile time. It examines every dereference
in your program and requires that the receiver is known to be non-null.
That could be because you have already checked it, or because it was
generated by a source that never produces null. The Nullness Checker has a
powerful analysis that keeps track of whether a reference might be null.
By comparison to use of Optional
, this reduces the number of
warnings and the number of redundant checks that are needed. By default,
the Nullness Checker assumes that references are non-null, but you can
specify possibly-missing data by writing
@Nullable
, as in the type @Nullable Point
.
Writing @Nullable Point
is
analogous to Optional<Point>
, but with significant benefits:
@Nullable
annotation on fields and method signatures — typically not within
method bodies.
Optional
.
There is no need to change interfaces and clients to use the
Optional
type, or to convert between Optional
instances and regular references.
Optional
is not present
on a type, you don't know whether the programmer forgot it, or
Optional
could not be written because of backward
compatibility, or the data is really always present. With the static
analysis of the Nullness Checker, the annotations are machine-checked at
compile time, so the program has @Nullable
on every
reference that might be null.
Map.get
, that Optional
is not
applicable to. It can also express method pre-conditions, which
are useful for fields containing possibly-missing data.
The Nullness Checker achieves the goal of
guaranteeing that you never forget to check for the presence of data,
anywhere in your code, in a way that is less disruptive than use of
Optional
. Other tools such as NullAway and Infer give similar
(sometimes slightly weaker) guarantees, with different trade-offs.
Since every programmer error related to null references is possible with
Optional
, and Optional
makes new types of errors
possible, programmers need support to avoid making all those errors. The
Checker Framework also contains a
compile-time Optional
Checker that does exactly that, and is useful if you have to
use Optional
(say, to interface with a library that
uses Optional
).
Optional
's handy methods
Although Optional
tends to clutter your code, if you have to use Optional
,
it provides methods that reduce the clutter. Here are two
examples:
orElse
returns the value if present, or else returns a default value
map
abstracts the pattern:
There are libraries that do the exact same things for
regular Java references.
An example is
the Opt
class that is distributed with the Checker Framework.
For each instance method in Optional
, the Opt
class
includes a static method.
Other methods such as filter
and flatMap
are described in the API documentation for Optional
.
These eliminate much of the need for calling Optional.isPresent()
and
Optional.get()
, which is a great benefit. However, they don't eliminate
all of it, and the other disadvantages of Optional
remain.
Not everyone is foolish enough to claim that Optional
solves
the problem underlying null pointer exceptions. For instance, Oracle's JDK
team does not claim this.
A general programming rule is to avoid, as much as possible, the situation
that data is not present. This reduces the need to write a type such as
Optional<Point>
or @Nullable Point
. All
the arguments of this article continue to hold, wherever in your program
data might not be present.
Some
people
suggest
that programmers should use Optional
sparingly, such as only
on method return types and never on fields. If you use
Optional
less, then there is less clutter, overhead, and
potential for misuse. However, the only way to eliminate
Optional
's problems is to not use it. Furthermore, if you use
Optional
less, you obtain fewer of its benefits. Null pointer
exceptions are important no matter what their source or syntactic form, so
the best solution is one that handles every reference in your program, not
just some of them.
The main argument for Optional
on return types is, "It's too
easy for clients to forget to handle the possibility of a null return
value. Optional
is ugly and in your face, so a client is
less likely to forget to handle the possibility of an empty return value.
Everywhere else, programmers should continue to use normal references,
which might be null."
By contrast, my suggestion is that programmers should continue to use
normal references everywhere in their program, but use a tool to ensure
that at every possible location — not just at method calls —
the program checks against null when needed. The
Nullness
Checker is a tool that serves this purpose, without the downsides of
Optional
.
If you find a style of using
Optional
that solves part of your problems, at acceptable
cost to you, then good for you — use it.
In conclusion, using regular Java references is better than special
libraries such as Optional
.
Optional
,
and the consequences (a run-time exception) are equally as severe.
Thus, Optional
alone doesn't solve any problems.
Optional
creates new potential problems.
Optional
's syntax is uglier than use of nullable references.
Optional
is less efficient than use of nullable references.
Optional
is to never forget to do a check.
Although Optional
does not guarantee this, other tools do.
Those tools, such as the Nullness Checker, are more powerful and precise, and they give a compile-time guarantee.
Thanks to Stuart Marks for comments on an earlier draft of this article.
Back to Advice compiled by Michael Ernst.
Michael Ernst