Optional
Back to the end of the belt. Sometimes the collector needs not a crate of everything that made it, but one box: the first one that passed all the stations. But what if nothing made it to the end?
The belt could hand back null. You have already met null: it looks like an ordinary reference until a method is called on it, and then the program fails with NullPointerException.
So the belt hands over its find in a clear case. The case either holds a box or is empty, and you can see which at once. Before taking the box, you look into the case. In Java, such a case is called Optional:
import java.util.List;
import java.util.Optional;
public class Main {
public static void main(String[] args) {
List<String> words = List.of("cat", "zebra", "ox");
Optional<String> found =
words.stream().filter(w -> w.length() > 4).findFirst();
Optional<String> missing =
words.stream().filter(w -> w.length() > 9).findFirst();
System.out.println(found.orElse("none")); // zebra
System.out.println(missing.orElse("none")); // none
}
}Let's go through the program line by line:
- In the first stream,
filterlets through only words longer than four letters, andzebrareaches the end. findFirst()is a terminal operation: it takes the first element that reached the end and puts it in anOptional<String>case.- In the second stream there are no words longer than nine letters, and
findFirst()returns an empty case. orElse("none")hands over what is in the case, or the fallback value"none"if the case is empty. So the program printszebraandnone.
A case instead of null
Optional is a container object that holds either one value or nothing. A method that may find nothing returns an Optional, and its type tells you right away that there may be no result. An ordinary reference gives no such warning.
If you print a whole case, println shows Optional[zebra] for a full one and Optional.empty for an empty one. Like findFirst, a stream's max and min return a case: an empty list has neither a longest nor a shortest word.
Interviewers often ask why Optional exists. The answer: so that a missing result shows in the type instead of hiding in null.
Getting the value out
A case has several ways to hand over what it holds, and each treats an empty case in its own way.
orElse
orElse(x) returns the value from the case, or the fallback value x for an empty one. There is always a result, which makes it the calmest way.
isPresent and get
isPresent() answers whether there is anything in the case, and get() takes the value out. Together they are written like this: the check first, then get().
ifPresent
ifPresent takes a Consumer and runs it only if there is a value. For an empty case it does nothing.
if (found.isPresent()) {
System.out.println(found.get().length()); // 5
}
found.ifPresent(w -> System.out.println("found " + w)); // found zebra
missing.ifPresent(w -> System.out.println("found " + w));Line by line:
found.isPresent()givestrue, soget()is safe and returns"zebra", and the print gives5.found.ifPresent(...)printsfound zebra.missing.ifPresent(...)prints nothing: the case is empty.
This is where the case picture is inaccurate. You can put another box into a real case, but an Optional does not change: it holds forever what the method put into it.
Common mistake
get() was called without looking into the case:
System.out.println(missing.get()); // ✗Exception in thread "main" java.util.NoSuchElementException: No value presentThe program fails: the case is empty, and get() throws an exception. It is the same trouble as with null, only under another name: the case did not help because nobody looked into it. Use orElse or ifPresent, and call get() only after checking isPresent().
Key points
Optionalis a case that holds either one value or nothing;findFirstreturns exactly that.orElse(x)hands over the value or the fallbackx, andifPresentruns an action only if there is a value.get()on an emptyOptionalthrowsNoSuchElementException, so it is not called without anisPresent()check.- An empty result in an
Optionalshows in its type, whilenullhides until the first method call.