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>
// Some(1), None, None, Some(3)
Option.some(null) throws: use Option.ofNullable for a value that may be null. Option.when calls its supplier
only when the condition holds.
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.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().