Option¶
Option<A> is a value that may be absent: Some(A value) or None(). It replaces java.util.Optional.
When to use it¶
Use Option when a value may be missing and there is nothing more to say about why: a lookup, an optional field.
- If the caller needs to know why the value is missing, return an
Either. - If the value comes from code that throws, use
Try.
Construction¶
var some = Option.some(1); // Option<Integer>
var none = Option.<Integer>none(); // Option<Integer>
var fromNullable = Option.<String>ofNullable(null); // Option<String>
var when = Option.when(3 > 2, () -> 3); // Option<Integer>
var unless = Option.unless(3 > 2, () -> 3); // Option<Integer>
// Some(1), None, None, Some(3), None
Option.some(null) throws: use Option.ofNullable for a value that may be null. Option.when calls its supplier
only when the condition holds, Option.unless only when it does not.
Pattern matching over the cases¶
Some and None are records, so a record pattern can take the value out and a when guard can add a condition.
Pattern matching with a switch expression needs no default.
var age = Option.some(17); // Option<Integer>
var label = switch (age) {
case Some(var years) when years >= 18 -> "adult";
case Some(var years) -> "minor, " + years;
case None() -> "unknown";
};
// "minor, 17"
fold(ifNone, ifSome) does the same with a Supplier and a Function.
Operations¶
filter, map and flatMap work on the value of a Some and leave a None alone. getOrElse gives the value
or a default.
var port = Option.some("8080")
.filter(s -> s.chars().allMatch(Character::isDigit))
.map(Integer::parseInt)
.getOrElse(80); // Integer
var shown = Option.<Integer>none().fold(() -> "no value", n -> "n = " + n);
// 8080, "no value"
mapTry runs a function that may throw, and returns a Try. On a None, the result is a Failure of a
NoSuchElementException.
var parsed = Option.some("x").mapTry(Integer::parseInt); // Try<Integer>
var absent = Option.<String>none().mapTry(Integer::parseInt); // Try<Integer>
// Failure(java.lang.NumberFormatException: For input string: "x"), Failure(java.util.NoSuchElementException: ...)
Other members:
isDefined()andisEmpty()test the case.tapruns an action on the value;tapNoneruns one when there is none.orElsegives anotherOptionwhen this one isNone.rejectkeeps the value only when it fails the test, the opposite offilter.collectisflatMapspelled the way the collections spell it.zipandzipWithcombine several options; see zip at arity N.
Conversions¶
toEither(Supplier),toTry(Supplier)andtoValidation(Supplier)supply the failure for aNone.toVector(),toList()andstream()give zero or one element.toOptional()andOption.ofOptional(Optional)go to and fromjava.util.Optional.
var fromOptional = Option.ofOptional(java.util.Optional.of(3)); // Option<Integer>
var either = fromOptional.toEither(() -> "missing"); // Either<String, Integer>
var validation = Option.<Integer>none().toValidation(() -> "missing"); // Validation<String, Integer>
var back = fromOptional.toOptional(); // java.util.Optional<Integer>
// Some(3), Right(3), Invalid(missing), Optional[3]
Sharp edges¶
map to null throws¶
Optional.map turns a null result into an empty Optional. Option.map throws a NullPointerException
instead, because Some cannot hold null.
To map to a value that may be absent, use flatMap with Option.ofNullable:
var env = java.util.Map.of("HOME", "/home/ada");
var shell = Option.some("SHELL").flatMap(key -> Option.ofNullable(env.get(key))); // Option<String>
// None, where map(env::get) would throw
get() on None throws¶
get() throws a NoSuchElementException on a None. Prefer pattern matching, fold or getOrElse.
Not a collection¶
An Option is not Iterable, so it cannot go in a for loop. Convert it with toVector() or stream().