Optional
A search method does not always find something. If it returns null in that case, the calling code can easily forget the check and crash with a NullPointerException. Optional is a container that either holds a value or is empty, and the type itself reminds you: "there may be no result".
Creating an Optional
Optional.of() wraps an existing value, Optional.empty() creates an empty container. The isPresent() and isEmpty() methods check whether there is a value inside:
void main() {
Optional<String> name = Optional.of("Anna");
Optional<String> nothing = Optional.empty();
IO.println(name.isPresent());
IO.println(nothing.isEmpty());
IO.println(name);
IO.println(nothing);
}Output:
true
true
Optional[Anna]
Optional.emptyA method that may not find anything
The main use of Optional is as the return type of a search method. If a value may turn out to be null, it is wrapped with Optional.ofNullable():
Map<String, String> phones = Map.of("Anna", "+49 30 1234567");
Optional<String> findPhone(String name) {
return Optional.ofNullable(phones.get(name));
}
void main() {
IO.println(findPhone("Anna"));
IO.println(findPhone("Oleg"));
}Output:
Optional[+49 30 1234567]
Optional.emptyWhoever calls findPhone() sees from the type that there may be no phone and has to decide what to do in that case.
A default value
orElse() returns the value from the container or a fallback if the container is empty. orElseGet() does the same but computes the fallback with a lambda — only when it is actually needed:
void main() {
Optional<String> city = Optional.empty();
IO.println(city.orElse("City not specified"));
IO.println(city.orElseGet(() -> "Berlin"));
}Output:
City not specified
BerlinTransforming: map and filter
map() applies a function to the value if there is one and returns a new Optional. filter() keeps the value only if it matches a condition. An empty Optional passes through them unchanged:
void main() {
Optional<String> email = Optional.of(" Anna@Mail.com ");
String result = email
.map(String::strip)
.map(String::toLowerCase)
.filter(e -> e.contains("@"))
.orElse("no address");
IO.println(result);
}Output:
anna@mail.comAn action if the value is present
ifPresent() runs an action only when there is a value, and ifPresentOrElse() also lets you describe a fallback branch. orElseThrow() returns the value or throws an exception if there is none:
void main() {
Optional<Integer> discount = Optional.of(10);
discount.ifPresent(d -> IO.println("Discount " + d + "%"));
Optional<Integer> none = Optional.empty();
none.ifPresentOrElse(
d -> IO.println("Discount " + d + "%"),
() -> IO.println("No discount"));
}Output:
Discount 10%
No discountCommon mistake
The get() method returns the value, but on an empty Optional it throws an exception. Calling it without a check is the same mistake as dereferencing null:
void main() {
Optional<String> phone = Optional.empty();
IO.println(phone.get());
}Output:
Exception in thread "main" java.util.NoSuchElementException: No value presentInstead of get(), use orElse(), ifPresent() or orElseThrow() with a clear message. And do not use Optional for fields and parameters — it is designed as a method's return type.