Optional type was introduced to denote absence of value in API.
Reasons behind Optional in Java
Speaking at a software conference called QCon London in 2009, Tony Hoare apologised for inventing the null
reference:
I call it my billion-dollar mistake. It was the invention of the null reference in 1965. At that time, I was designing the first comprehensive type system for references in an object oriented language. My goal was to ensure that all use of references should be absolutely safe, with checking performed automatically by the compiler. But I couldn’t resist the temptation to put in a null reference, simply because it was so easy to implement. This has led to innumerable errors, vulnerabilities, and system crashes, which have probably caused a billion dollars of pain and damage in the last forty years.
In Java API documentation Optional
is described as:
A container object which may or may not contain a non-null value.
Use Cases
Denote absence of value in API
There are no use cases, there is the only one use case! As Vitaly Davidovich states:
Optional is being added for the value it offers in “fluent” sequences of statements.
Optional is not supposed to solve null reference problems. It is supposed to denote that value is optional - it could exist or could not exist.
Usage of Optional
type in API significantly improves readability and could prevent
errors which could be caused if null
is used to denote absence of value.
Method with signature:
public Comission comissionFor(Customer customer);
Does not tell how to determine that might be a situation without commission for a customer and even if method returns null to determine absence of commission. How developer could be sure that this is not an error?
And on the other hand:
public Optional<Comission> comissionFor(Customer customer);
Directly says that there is a potential situation without commission and if method
returns null
, there is a bug in the code.
So, if there is need to denote absence of value and null
is used to solve this issue,
Optional is more preferred way to do it.
Disadvantages
Performance
The Optional
type is additional wrapper and requires memory to be allocated for it. And in case if you have
a lot of objects wrapped into Optional and high performance is requirement, it could be a real problem.
In future versions (or at reading time it maybe current version) value types could remove this drawback.
Serialization
Optional type is not Serializable, because it was not supposed to be used in fields. By design, Optional is supposed to be used as return type.
Meaningless types
In case of overuse of Optional, things like:
Map<Integer, Optional<List<Optional<Integer>>>>
Could be very hard to understand. But this problem could be solved by providing meaningful types, which could be closer to domain.
Debugging
Wrapping objects into Optional also could lead to larger call stack, which also complicates debugging.
Still throwing exceptions
The Optional
type still could throw exception if not used properly. Replacing all references to Optional objects
would not help to avoid exceptions. API should be accurately designed to denote potential absence of values. And
requirement to work carefully with such API is left.
In case if it is used just to remove null
checks, it is better to consider tools like Nullness Checker.
And, by the way, this tool perfectly complements Optional
type.
Optional API
Optional.of, Optional.ofNullable and Optional.empty
In short:
- Methods
of
andempty
could serve best in new code for constructing Optional values. - Method
ofNullable
could be used as wrapper for old API which usesnull
value to determine absence of value.
The constructor of Optional is private and method of
serves as static factory for creating Optional values:
Optional<BigDecimal> money = Optional.of(BigDecimal.valueOf(10000));
System.out.println(money);
// Optional[10000]
It will throw exception in case if null
is passed:
Optional.of(null);
// Exception in thread "main" java.lang.NullPointerException
There is a dedicated method for handling null
values and converting them to
“empty” Optional objects - ofNullable
:
BigDecimal nullableMoney = null;
Optional<BigDecimal> optionalMoney = Optional.ofNullable(nullableMoney);
System.out.println(optionalMoney);
// Optional.empty
In case if there is need for empty Optional object which determines that there is no value:
Optional<BigDecimal> optionalMoney = Optional.empty();
System.out.println(optionalMoney);
// Optional.empty
Optional.isPresent, Optional.ifPresent and Optional.ifPresentOrElse
Methods isPresent
, ifPresent
and ifPresentOrElse
are imperative relatives. E.g. method isPresent
is usable in
pair with get
. It is a common pattern in “imperative” style, to test if value present and get it. Or method ifPresent
could be used to perform operation with side-effect if there is value.
So, isPresent
tests if value is present:
Optional<BigDecimal> optionalEmpty = Optional.empty();
System.out.println(optionalEmpty.isPresent());
// false
Optional<BigDecimal> optionalFromNull = Optional.ofNullable(null);
System.out.println(optionalFromNull.isPresent());
// false
Optional<BigDecimal> optionalWithValue = Optional.of(BigDecimal.TEN);
System.out.println(optionalWithValue.isPresent());
// true
Also an operation could be performed if value is present:
Optional<BigDecimal> optionalWithValue = Optional.of(BigDecimal.TEN);
optionalWithValue.ifPresent((x) -> System.out.println(x.add(BigDecimal.TEN)));
// 20
Method ifPresent
does not return a value, it just performs operation if value is present.
In case if there is need to act if there is no value, ifPresentOrElse
is more suitable:
Optional<BigDecimal> optionalWithValue = Optional.of(BigDecimal.TEN);
optionalWithValue.ifPresentOrElse(
(v) -> System.out.println(v.multiply(BigDecimal.TEN)),
() -> System.out.println("There is no value")
);
// 100
Optional<BigDecimal> emptyOptional = Optional.empty();
emptyOptional.ifPresentOrElse(
(v) -> System.out.println(v.multiply(BigDecimal.TEN)),
() -> System.out.println("There is no value!")
);
// There is no value!
Optional.get, Optional.or, Optional.orElse, Optional.orElseGet, Optional.orElseThrow
Ideally operations like get
should be used once or should not used at all. In case if
Optional is used with isPresent
and get
it is almost equivalent to “nullable value”. Because
get
also could throw an exception and developer should keep in mind to check if there is a value. There are better ways
to work with Optional, e.g. map
, filter
or stream
.
Anyway, if there is need to use to get
check for isPresent
is mandatory:
Optional<BigDecimal> optionalWithValue = Optional.of(BigDecimal.TEN);
System.out.println(optionalWithValue);
// Optional[10]
System.out.println(optionalWithValue.get());
// 10
Optional.empty().get();
// Exception in thread "main" java.util.NoSuchElementException: No value present
Method orElse
allows to define default value in case if there is no value:
Optional<Integer> optionalWithValue = Optional.of(10);
int x = optionalWithValue.orElse(20);
System.out.println(x);
// 10
Optional<Integer> emptyOptional = Optional.empty();
int y = emptyOptional.orElse(100);
System.out.println(y);
// 100
In simple example with literal 100, it works fine. But if “orElse” semantic could require computation it is better
to use orElseGet
which allows to specify function and evaluates its lazily only if there is no value:
Optional<Integer> emptyOptional = Optional.empty();
int y = emptyOptional.orElseGet(() -> 100);
System.out.println(y);
// 100
In case if there is need to use return Optional
as default value, there is the or
method:
Optional<String> abc1 = Optional.of("abc1");
Optional<String> abc2 = Optional.of("abc2");
Optional<String> emptyOptional = Optional.empty();
Optional<String> abc1OrAbc2 = abc1.or(() -> abc2);
System.out.println(abc1OrAbc2);
// Optional[abc1]
Optional<String> abc1OrEmpty = abc1.or(() -> emptyOptional);
System.out.println(abc1OrEmpty);
// Optional[abc1]
Optional<String> emptyOrAbc1 = emptyOptional.or(() -> abc1);
System.out.println(emptyOrAbc1);
// Optional[abc1]
Why to use orElseThrow
which throws NoSuchElementException
exception if it is identical to get
method?
Optional<Integer> emptyOptional = Optional.empty();
int x = emptyOptional.orElseThrow();
// Exception in thread "main" java.util.NoSuchElementException: No value present
Semantics. Usage orElseThrow
shows that a developer aware of possible exception and also shows to the reader of
code that there could be an exception.
Version of orElseThrow
with argument allows to specify exception:
Optional<Integer> emptyOptional = Optional.empty();
int x = emptyOptional.orElseThrow(() -> new IllegalArgumentException("There is no value!"));
// Exception in thread "main" java.lang.IllegalArgumentException: There is no value!
Optional.map, Optional.filter, Optional.flatMap, Optional.stream
A “functional way” to work with Optional type is to avoid extracting value from Optional (get
method)
and to avoid producing side-effects (like exceptions).
Filter
Shortly speaking, filter
allows to check if value wrapped in Optional
satisfies condition:
Optional<String> abc1 = Optional.of("abc1");
Optional<String> afterFilterDef2= abc1.filter((v) -> v.equals("def2"));
System.out.println(afterFilterDef2);
// Optional.empty
Optional<String> afterFilterAbc1 = abc1.filter((v) -> v.equals("abc1"));
System.out.println(afterFilterAbc1);
// Optional[abc1]
Use case example:
request.cookies.get("userId").map((c) -> Integer.valueOf(c.value)).filter(_ > 0)
Map
To preserve “unwrapping” value from Optional
and map it to another value there is a map
function:
Optional<String> abc1 = Optional.of("abc1");
Optional<Integer> abc1Length = abc1.map(String::length);
System.out.println(abc1Length);
// Optional[4]
FlatMap
Difference between flatMap
and map
:
public class App {
static class SubscriptionPlan {
Optional<BigDecimal> requiredPayment() {
return Optional.of(BigDecimal.TEN);
}
}
static class Customer {
Optional<SubscriptionPlan> subscriptionPlan() {
return Optional.of(new SubscriptionPlan());
}
}
public static void main(String[] args) {
BigDecimal amountToWithdraw =
new Customer()
.subscriptionPlan()
.flatMap(SubscriptionPlan::requiredPayment)
.orElse(BigDecimal.ZERO);
System.out.println(amountToWithdraw);
// 10
}
}
How this could be implemented with map
?
Stream
Optional
has stream
?
public static void main(String[] args) {
var l = List.of(1, 2, 3, 9, 10, 25, 36);
var integerRoots = l.stream().flatMap(n -> integerSquareRoot(n).stream()).collect(Collectors.toList());
System.out.println(integerRoots);
}
private static Optional<Integer> integerSquareRoot(int number) {
double squareRoot = Math.sqrt(number);
if ((int)squareRoot * (int)squareRoot == (int)number) {
return Optional.of((int) squareRoot);
}
return Optional.empty();
}
This is good example of flatMap
and stream
combination.
Optional.equals, Optional.hashCode and Optional.toString
Method equals
is implemented in a way that a developer could think that
underlying values are compared:
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof Optional)) {
return false;
}
Optional<?> other = (Optional<?>) obj;
return Objects.equals(value, other.value);
}
To avoid misunderstanding:
Optional<String> abc1 = Optional.of("abc");
Optional<String> abc2 = Optional.of("abc");
Optional<String> xyz = Optional.of("xyz");
Optional<String> empty1 = Optional.empty();
Optional<String> empty2 = Optional.empty();
Optional<String> emptyString = Optional.of("");
System.out.println(abc1.equals(abc2));
// true
System.out.println(abc1.equals(xyz));
// false
System.out.println(abc1.equals(empty1));
// false
System.out.println(empty2.equals(emptyString));
// false
System.out.println(empty1.equals(empty2));
// true
Method hashCode
also uses hashCode
function on underlying value:
@Override
public int hashCode() {
return Objects.hashCode(value);
}
E.g. for String:
System.out.println(Optional.of("abc").hashCode());
// 96354
System.out.println("abc".hashCode());
// 96354
So, Optional values could be compared or used as dictionary keys if underlying values do not restrict this.
Method toString
prints human-readable contents of Optional object:
System.out.println(Optional.of("some value"));
// Optional[some value]
System.out.println(Optional.of(""));
// Optional[]
System.out.println(Optional.empty());
// Optional.empty
In other languages
Option in Scala
In Scala there is Option
monad which sounds quite similar to Optional
in Java, but really it is not. In Java
Optional
has been introduced to solve simple issue - denote that function may return anything, but in Scala
Option
type is first-class citizen which plays very important role as part of language.
Option
in Scala has a very powerful API and it is
Serializable.
In Scala it is integrated into standard library, e.g. Map.get(key):
trait Map[K, +V] ... {
// ...
abstract def get(key: K): Option[V]
// ...
}
In Scala Option
supports pattern matching:
val nameMaybe = request getParameter "name"
nameMaybe match {
case Some(name) =>
println(name.trim.toUppercase)
case None =>
println("No name value")
}
It is also supports collection-like functions:
val name = Some("John")
name.foreach(n => println(n))
// John
An integral part of the language.
Maybe in Haskell
In Haskell there is no concept of nil
or null
value to say that function does not return a value. But there is
a very straightforward way to say it - monad Maybe
:
data Maybe a = Just a | Nothing
This Maybe
holds just a value or represents Nothing
.
Optional in Java is not a monad
Optional type in Java “behaves like” a monad. It has constructor unit
- ofNullable
and bind
- flatMap
methods.
And it seems that it could be as the monad, but it could not. Because Optional breaks the monad laws.
Developer should use Optional
type in Java very carefully.
Summary
Optional type allows to increase readability and prevent errors if used carefully, so it is recommended to:
- Avoid using optionality at all.
- But if it is not possible, prefer
Optional
instead ofnull
. - In performance critical blocks, local or private code blocks,
Optional
could be omitted.
Links
- http://www.baeldung.com/java-optional
- https://www.oracle.com/technetwork/articles/java/java8-optional-2175753.html
- https://dzone.com/articles/java-8-optional-whats-point
- https://homes.cs.washington.edu/~mernst/advice/nothing-is-better-than-optional.html
- https://checkerframework.org/manual/#nullness-checker
- https://blog.codefx.org/java/dev/why-isnt-optional-serializable/
- https://danielwestheide.com/blog/2012/12/19/the-neophytes-guide-to-scala-part-5-the-option-type.html
- https://pbrisbin.com/posts/maybe_is_just_awesome/
- http://cr.openjdk.java.net/~jrose/values/values-0.html
- https://danielwestheide.com/blog/2016/04/26/when-option-is-not-good-enough.html
- https://www.sitepoint.com/how-optional-breaks-the-monad-laws-and-why-it-matters/
- https://medium.com/@afcastano/monads-for-java-developers-part-1-the-optional-monad-aa6e797b8a6e
- https://medium.com/coding-with-clarity/understanding-the-optional-monad-in-java-8-e3000d85ffd2
- https://www.scala-lang.org/api/current/scala/Option.html