Usage Examples
Now that we learned why Optional
s are used, let us take a look what we can actually do with them in java. These
code examples (and Sponge) use the java.util.Optional
class available in Java 8.
Obtaining the wrapped Value
The get()
method will unwrap an Optional
and return the wrapped value. If no value is present, calling get()
will throw a NoSuchElementException
, so a presence check should be performed first.
Optional<String> opt = getOptionalString();
String wrappedString = opt.get();
Handling absent Values
The purpose of the Optional
type is representing a value that might or might not be there. As such, many use cases
revolve around handling absent values.
Presence Check
The isPresent()
method returns true if a value is present on the Optional
. It can provide the most basic
verification and is an equivalent to a classic null
check.
Optional<String> opt = getOptionalString();
if (opt.isPresent()) {
String wrappedString = opt.get();
// more code
}
Using default Values
A common pattern is falling back to a default value if none is present. The orElse()
method allows for a single
line statement that will return either the value present on the Optional
or the supplied default value.
Instead of
Optional<String> optionalString = optionalString();
String someString;
if (optionalString.isPresent()) {
someString = optionalString.get();
} else {
someString = DEFAULT_STRING;
}
just use
String someString = getOptionalString().orElse(DEFAULT_STRING);
In some cases a default value has to be calculated in a way that has side effects or is particularly expensive. In such
a case it is desirable to calculate the default value only if needed (lazy evaluation). The orElseGet()
method
accepts a Supplier
instead of a pre-calculated value. If no value is present on the Optional
itself, the
Supplier
will be called. Since Supplier
is a functional interface, a lambda expression or method reference can
be passed instead.
Instead of
Optional<String> optionalString = optionalString();
String someString;
if (optionalString.isPresent()) {
someString = optionalString.get();
} else {
someString = myPlugin.defaultString();
}
just use
String someString = getOptionalString().orElseGet(myPlugin::defaultString);
Fail on absent Values
If a value being absent should lead to an exception, it is almost always better to throw a custom exception instead of
relying on the default NoSuchElementException
. If you call the orElseThrow()
method with a Supplier
, it will
return the wrapped value if it is present, or throw a Throwable
obtained from the Supplier
if the Optional
is empty. Again, as Supplier
is a functional interface, lambda expressions or method references may be used instead.
Instead of
Optional<String> optionalString = optionalString();
if (!optionalString.isPresent()) {
throw new MyException();
}
String someString = optionalString.get();
just use
String someString = getOptionalString().orElseThrow(MyException::new);
Примітка
If the Throwable
provided by the supplier is a checked exception, it will also have to be included in the
signature of the surrounding function (for example public void doStuff() throws MyException
)
Conditional Code Execution
If no default value can be used, the code that relies on a value being present cannot be executed. While this might be dealt with in a simple condition, there are other convenient methods.
Consuming Values
If your logic to handle the present value is already encapsulated in a Consumer
or a single-parameter function, the
ifPresent()
method will accept the consumer (or a method reference). If a value is present on the Optional
, it
will be passed to the consumer. If the Optional
is empty, nothing will happen.
Instead of
Optional<String> optionalString = getOptionalString();
if (optionalString.isPresent()) {
myPlugin.doSomethingWithString(optionalString.get());
}
just use
Optional<String> optionalString = getOptionalString();
optionalString.ifPresent(s -> myPlugin.doSomethingWithString(s));
or
getOptionalString().ifPresent(myPlugin::doSomethingWithString);
Filtering
It is also possible to pass a Predicate
. Only values that this Predicate
returns true for will be retained. If
no value is present or the Predicate
returns false
, an empty Optional
will be returned. Since this method
returns an optional, it allows for chaining with other methods.
Instead of
Optional<String> optionalString = getOptionalString();
if (optionalString.isPresent()) {
String someString = optionalString.get();
if (stringTester.isPalindromic(someString)) {
myPlugin.doSomethingWithString(someString);
}
}
just use
getOptionalString()
.filter(stringTester::isPalindromic)
.ifPresent(myPlugin::doSomethingWithString);
Примітка
Neither this filtering function nor the mapping functions described below modify the instance they are called on.
Optional
s are always immutable.
Mapping
Another chainable operation is mapping the potential value to a different one. If no value is present, nothing will
change. But if it is present, the map()
method will return an Optional
of the value returned by the provided
Function
(or an empty Optional
if that return value is null
).
Instead of
Optional<String> optionalString = getOptionalString();
if (optionalString.isPresent()) {
String someString = optionalString.toLowerCase();
myPlugin.doSomethingWithString(someString);
}
just use
getOptionalString()
.map(s -> s.toLowerCase())
.ifPresent(myPlugin::doSomethingWithString);
Порада
If your mapping function already returns an Optional
, use the flatMap()
method instead. It will behave just
like map()
, except that it expects the mapping function to already return an Optional
and therefore will
not wrap the result.
Combined Example
Imagine a plugin that allows each player to have a pet following. Assume the existance of the following methods:
petRegistry.getPetForPlayer()
accepting aPlayer
and returning anOptional<Pet>
. This method looks up the pet associated with a given playerpetHelper.canSpawn()
accepting aPet
and returning aboolean
. This method performs all the necessary checks to make sure the given pet may be spawned.petHelper.spawnPet()
accepting aPet
and returning nothing. This method will spawn a previously not spawned pet.
Now from somewhere (probably the execution of a command) we got the optionalPlayer
variable holding an
Optional<Player>
. We now want to obtain this players pet, check if the pet is spawned and if it is not spawned,
spawn it while performing the according checks if each and every Optional
actually contains a value. The code only
using the basic isPresent()
and get()
methods gets nasty really quickly.
if (optionalPlayer.isPresent()) {
Player player = optionalPlayer.get();
Optional<Pet> optionalPet = petRegistry.getPetForPlayer(player);
if (optionalPet.isPresent()) {
Pet pet = optionalPet.get();
if (petHelper.canSpawn(pet)) {
petHelper.spawnPet(pet);
}
}
}
However through use of Optional
s methods for conditional code execution, all those presence checks are hidden,
reducing the boilerplate and indentation levels and thus leaving the code much more readable:
optionalPlayer
.flatMap(petRegistry::getPetForPlayer)
.filter(petHelper::canSpawn)
.ifPresent(petHelper::spawnPet);
Creating Optionals
Should you choose to provide an API following the same contract of using Optional
instead of returning null
values, you will have to create Optional
s in order to be able to return them. This is done by calling one of the
three static constructor methods.
Optional.empty()
will always return an empty Optional
.
Optional.of()
will return an optional wrapping the given value and raise a NullPointerException
if the value was
null
.
Optional.ofNullable()
will return an empty Optional
if the supplied value is null
, otherwise it will return
an Optional
wrapping the value.