Usage Examples
Now that we learned why Optionals 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);
Note
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);
Note
Neither this filtering function nor the mapping functions described below modify the instance they are called on.
Optionals 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);
Tip
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 aPlayerand returning anOptional<Pet>. This method looks up the pet associated with a given playerpetHelper.canSpawn()accepting aPetand returning aboolean. This method performs all the necessary checks to make sure the given pet may be spawned.petHelper.spawnPet()accepting aPetand 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 Optionals 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 Optionals 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.