Optionals
Goals
- Recognize the drawbacks of
null
references. - Learn how to use
Optional<T>
in APIs to mitigate the risks ofnull
references.
Concepts
- null
- optional-return idiom
Language
null
Library
java.util.NoSuchElementException
java.util.Optional<T>
java.util.Optional.empty()
java.util.Optional.flatMap(Function<? super T, Optional<U>> mapper)
java.util.Optional.filter(Predicate<? super T> predicate)
java.util.Optional.get()
java.util.Optional.ifPresent(Consumer<? super T> consumer)
java.util.Optional.isPresent()
java.util.Optional.map(Function<? super T, ? extends U> mapper)
java.util.Optional.of(T value)
java.util.Optional.ofNullable(T value)
java.util.Optional.orElse(T other)
java.util.Optional.orElseThrow(Supplier<? extends X> exceptionSupplier)
java.util.OptionalDouble
java.util.OptionalInt
java.util.OptionalLong
java.util.function.Consumer<T>
java.util.function.Function<T, R>
java.util.function.Predicate<T>
java.util.function.Supplier<T>
Dependencies
org.github.npathai:hamcrest-optional:1.0
(scope:test
)
Preview
final Car car = …;
car.getTrunk()
.flatMap(Trunk::getSpareTire)
.ifPresent(car::changeFlat);
Lesson
One of the most pesky issues to tackle in Java, as well as in many programming languages, is the the appearance of null values. (You have no doubt already encountered many bugs related to null
in your homework during this course.) The first difficulty null
exhibits is remembering whether a method's return value needs to be checked for null
. The @Nullable
annotation from JSR-305 mitigates this problem somewhat, but after recognizing that null
may be returned the next difficulty is how to handle null
in an elegant manner.
But even after identifying and handling them, null
values make your program less understandable and less safe. A null
value has no type, so there is sometimes no way to know what an object reference should have been, had it not been null
. And in fact null
in itself doesn't have clear semantics; does it mean that a value is missing, that it is unavailable, or that some error occurred?
Optional<T>
Java now provides a construct that not only makes it clear when a value may not be present, but also provides semantically rich methods for dealing with null
—at the same time being especially friendly to functional expressions, and the lambdas and method references used with them. The java.util.Optional<T>
class represent a value that is optional
—a value object that may be empty
and not represent any value.
To see how Optional<T>
can help the null
problem, let's see a typical case where the null
problem might arise. Consider a car that develops a flat tire, and you wish to change the flat using the spare tire in the trunk (also called a boot
in the United Kingdom). You may have an API with Car
, Trunk
, and Tire
interfaces.
Two problems immediately present themselves: not all cars have trunks, and not every trunk contains a spare tire! Your API might reflect these possibilities using null
:
The problem is that there are two chances to get a null
reference: when calling Car.getTrunk()
and when calling Trunk.getSpareTire()
. You must account for the possibility of null
by checking for the presence of a value before using each nullable reference. This is simple to do (if you remember), but soon gets verbose and cumbersome.
Replacing both of these return values with Optional<T>
provides even better documentation than @Nullable
ever could that these methods might not return a value.
You can retrieve the value contained in an Optional<T>
by using the Optional.get()
method, as shown in the above example. If the Optional<T>
is empty (that is, if no value was returned), Optional.get()
will throw a java.util.NoSuchElementException
. But now we seem to be back where we started. Have we simply traded the possibility of a NullPointerException
for a NoSuchElementException
?
Keep in mind the value of Optional<T>
as documentation, indicating the possibility of a missing
value. But documentation is not the only benefit of Optional<T>
, as you'll see in the following sections. Indeed it is only the start.
Checking Value Presence
Optional.isPresent()
When using Optional<T>
you can avoid a NoSuchElementException
as you would avoid a NullPointerException
: by first checking if a value is present before continuing processing. The Optional.isPresent()
method indicates whether the Optional<T>
instance contains a value, and can be used wherever you would normally place a null
check.
Consider the car's registration plate, which may not be available if the car is new has not yet been sold. Above a null
check is performed using the ternary operator: registration != null ? registration : "unregistered"
. If the registration were returned as an Optional<String>
, the check could be done in a similar manner using Optional.isPresent()
:
Being forced to use Optional.isPresent()
combined with Optional.get()
is perhaps more cumbersome than null
checks, but the use of Optional<T>
alone does have one benefit: it makes it difficult to forget that the value is optional and that checks are needed. Optional<T>
however provides additional methods that parallel many standard use cases for null
handling, and these capabilities combine to make the use of Optional<T>
more elegant than standard null
checks.
Optional.orElse(T)
The use of the ternary operator for null
checking is a common one. Its goal is to return the value if present, or an alternate or default value if the original value is not present, as you saw with registration.isPresent() ? registration.get() : "unregistered"
above. Optional<T>
provides a convenient method Optional.orElse(T other)
which performs exactly this functionality.
Optional.orElseThrow(…)
Depending on the requirements, the absence of a valid vehicle registration may be considered an error condition. Perhaps in real life the service dispatcher should have checked for the presence of a registration plate before sending a service technician. During the changing of the flat, if the registration number is not present it represents an invalid state, and an InvalidStateException
should be thrown.
The Optional.orElseThrow(Supplier<? extends X> exceptionSupplier)
allows a custom exception to be thrown if the value is not present. Rather than wastefully creating an exception instance each time, this method allows a java.util.function.Supplier<T>
to be provided that can supply a new Throwable
when needed. As Supplier<T>
is a functional interface, you should recognize that Optional.orElseThrow(…)
is a prime candidate for a lambda or a method reference.
Optional.ifPresent(…)
One other Optional<T>
method for checking for the presence of a value is Optional.ifPresent(Consumer<? super T> consumer)
. This method takes a java.util.function.Consumer<T>
that will be invoked if the value is present; otherwise, no action will be performed.
At first glance it may seem that Optional.ifPresent(…)
provides little value over an explicit check using if(optional.isPresent()) { … }
following by the action to perform. That Consumer<T>
is a functional interface, however, brings some other possibilities. Going back to the original example above, one could provide a method reference to Car.changeFlat(Tire)
rather than calling it explicitly.
Processing Values
Mapping
Optional.map(…)
Often getting the optional value is not your final goal—it is only a means for retrieving some other value from the first one. Let's say that you (possibly) have a spare tire, and you want to make sure the valve stem is closed. This would involve asking the Tire
for its ValveStem
as illustrated below. Note that when this API was first created, it was assumed that every tires had a valve stem, and thus Tire.getValveStem()
returns a reference to the ValveStem
itself, not an Optional<ValveStem>
.
This is getting complicated! Because Trunk.getSpareTire()
returns an Optional<Tire>
, we would need need to add something like if(spare.isPresent())
before calling Tire.getValveStem()
. And because the returned ValveStem
may be null
, we would additionally need to add if(valveStem != null)
before calling valveStem.close()
.
What we are really wanting to do is to map
the the Tire
to its ValveStem
so that we can close it. You will note that the method Tire.getValveStem()
is invoked on a Tire
instance and produces a ValveStem
instance. If we have an instance of Tire
, in other words, we can produce a ValveStem
(which may be null
) for that Tire
—just as we could do with a Map<Tire, ValveStem>
, if we had such a map available.
The method Optional.map(Function<? super T, ? extends U> mapper)
allows us to map an optional value to another value in just such a way. The output will itself be an Optional<T>
, for two reasons. First, if the original value is not present, it cannot be mapped to another value. Second, the mapping itself may produce null
; whatever value is produced is therefore wrapped in an Optional<T>
. The mapper parameter is a java.util.function.Function<T, R>
representing the mapping function
between the two values. Here the method used for mapping is Tire.getValveStem()
, and you can use a method reference to meet the requirements of Function<Tire, ValveStem>
, as shown below.
By recognizing that we were mapping from an optional value to one of its properties; and by leveraging Optional.map(…)
, Optional.ifPresent(…)
, and method references; what was originally a precarious set of decisions has now been turned into an a safe, concise set of functions that together read almost like a spoken language sentence.
Optional.flatMap(…)
Your first impulse when analyzing the above code may be to replace the if(trunk.isPresent())
test with a similar use of Optional.map(…)
from Trunk to Tire using the Trunk.getSpareTire()
method as the mapping function.
You might have forgotten that Trunk.getSpareTire()
already returns an Optional<Tire>, and because Optional.map(…)
wraps the result of the mapper with an Optional<T>
, the result is an Optional<Optional<Trunk>>
. This is more layers of indirection than we need!
The solution is Optional.flatMap(Function<? super T, Optional<U>> mapper)
, which functions identically to Optional.map(…)
except that Optional.flatMap(…)
returns any resulting Optional<T>
as-is without wrapping it in an additional Optional<T>
.
Filtering
When studying lambdas we discussed how that you can remove items from a collection by calling Collection.removeIf(Predicate<? super E> filter)
and passing a java.util.function.Predicate<T>
to test each element individually and determine if it should be removed. In an even earlier lesson you saw that Google's Guava library has filtering utility methods such as Iterators.filter(Iterator<T> unfiltered, Predicate<? super T> predicate)
, for which the given Guava predicate encapsulates the test for filtering individual elements of the Iterator<T>
.
The Optional.filter(Predicate<? super T> predicate)
method provides a way to filter
an optional value. The using a Predicate<T>
parameter indicates the test to apply to the value. If the value passes the test, the returned Optional<T>
will likely be the same Optional<T>
you started with. If the value does not pass the test (or there is no value to begin with), the returned Optional<T>
will be empty.
Let's suppose that a service technician is only authorized to change a flat tire on a car with a registration plate that starts with foo
. If Optional<T>
were not used, the service technician would first have to check the registration plate (which may be null
) and then compare its beginning characters with the string "foo"
.
Filtering with Optional<T>
becomes easier, safer, and more readable:
Producing an Optional<T>
You can wrap any instance of an object with Optional<T>
using the static factory method Optional.of(T value)
, as you should expect. What you might not have expected is that Optional.of(…)
will throw a NullPointerException
if the given value is null
. If you want an instance of Optional<T>
that contains no value, you can use Optional.empty()
.
To see how this works, let's create a method for a Point
to indicate in which quadrant it lies, taking into consideration that a Point
on one of the axes (that is, with a X and/or Y coordinate of zero) is not considered to be in any quadrant. We'll create an enum to represent the four quadrant values.
If you already have a reference may may be null
, you can create an Optional<T>
instance using Optional.ofNullable(T value)
. This is especially useful if you are working with a legacy API (such as the Java Collections Framework) that does not support Optional<T>
. Let us revisit the VehicleRepository
from a previous lesson and have lookup based upon registration number return an Optional<Vehicle>
instead of @Nullable Vehicle
. A typical implementation might use a Map<String, Vehicle>
to associate vehicles with registration numbers, which are unique identifiers. As Map.get()
returns a nullable reference, it is a simple step to return an Optional<Vehicle>
by using Optional.ofNullable(…)
.
Drawbacks
The Optional<T>
class brings a lot of benefits, but it isn't a perfect tool, as some of the articles in the See Also section demonstrate. Sometimes it makes code safer and more readable. Sometimes it makes simple logic more complicated. And Optional<T>
still is no better than null
for indicating why a value is missing.
The main purpose of Optional<T>
was to make it easier for an API to indicate that a return value is optional. Some go so far as to advocate that Optional<T>
should never be used as input parameters of methods or in constructor parameters. In your own code try to relegate the use of Optional<T>
as an optional-return idiom as intended unless the benefit of using it for another purpose is significant.
Review
Gotchas
- The output of
Optional.map(…)
is anOptional<T>
, so if the mapping function also produces anOptional<T>
, this would produce anOptional<Optional<T>>
. You would need to useOptional.flatMap(…)
instead in this case. Optional.of(…)
will throw aNullPointerException
if the given value isnull
. If you don't know whether a given reference isnull
(such as when converting from legacy APIs), useOptional.ofNullable(…)
.- Avoid storing references to
Optional<T>
in your business objects, as this can cause problems using certain object storage mechanisms.
In the Real World
- The primitive counterparts of
Optional<T>
(OptionalInt
,OptionalLong,
andOptionalDouble
) have features removed. In most cases stick withOptional<T>
instead of the primitive versions. - Retrieving an optional value using
Optional.get()
can be almost as dangerous as accessing a nullable reference; use a more functional alternate if you can. - When mapping several layers deep into an
Optional<T>
, if one of the mapping methods itself returns anOptional<T>
useOptional.flatMap(…)
instead ofOptional.map(…)
.
Think About It
Filtering
an optional value makes more sense if you think of anOptional<T>
as analogous to aIterator<T>
that can return at most a single object.
Self Evaluation
- What part specifically of an API was the
Optional<T>
class meant to improve? - What are some of the drawbacks of
Optional<T>
?
Task
Upgrade your PublicationRepository
API to use Optional<T>
for single-publication lookup by name.
Improve the command-line interface of Booker so that it accepts a --name
(-n
) parameter to the list
command, indicating the name of a single publication to list. If no --name
is indicated, list all the publications as you do now. Use a getter method returning Optional<String>
in your command line options class to indicate whether a name parameter was passed, trimming the string of beginning and trailing whitespace making use of Optional<T>
methods. In the presentation layer operations such as trimming whitespace may be considered normalization when converting from user data to business data. You may need to use quotes when testing from the command line.
booker list [--name <name>]
booker -h | --help
Option | Alias | Description |
---|---|---|
list | Lists all available publications. | |
--help | -h | Prints out a help summary of available switches. |
--name | -n | Indicates a filter by name for the list command. |
See Also
- Null References: The Billion Dollar Mistake (Tony Hoare, InfoQ)
- The worst mistake of computer science (Paul Draper, Lucidchart)
- Tired of Null Pointer Exceptions? Consider Using Java SE 8's Optional! (Raoul-Gabriel Urma, Oracle)
- Optional in Java 8 cheat sheet (Tomasz Nurkiewicz)
- Java 8 Optional: How to Use it (JHadesDev)
- Optional Will Remain an Option in Java (Lukas Eder)
- Why
java.util.Optional
is broken (Jed Wesley-Smith, Atlassian Developers)