Lesson 55Phase 6: Functional and modern Java
Optional: a value that might be missing
Saying "there may be no result" in the type itself instead of returning null, and the Optional methods that make it pleasant.
By BasithUpdated 40 min
- Optional
- of
- ofNullable
- empty
- orElse
- orElseThrow
A library catalog has a method that finds a book by its id. Ask it for book 7 when there is no book 7, and what should it hand back? For a long time the usual answer in Java was null, and the usual result was a NullPointerException a few lines later. This lesson is about Optional, a small class from the Java library that puts “there may be no result” into a method's return type, where the compiler can see it, and about the methods that make the missing case quick to handle.
- Explain why returning
nullfor “nothing found” leads to aNullPointerException, and how anOptionalreturn type prevents it. - Make an
Optionalwithof,ofNullableandempty, and say which one fits a given value. - Get the value out with
orElse,orElseGetandorElseThrow, and explain whyorElsealways runs its argument. - Change and test the value inside with
map,filterandflatMap, and act on it withifPresent,ifPresentOrElse,orandstream. - Recognize the stream methods that return an
Optional, anOptionalIntor anOptionalDouble, such asfindFirst,maxandaverage. - Use
Optionalas a return type and almost nowhere else, and never callget()without a check.
null from the this keyword and object references: a reference that points at no object, and the NullPointerException you get when you use the dot on it. In HashMap and HashSet, get returned null for a key the map did not have. In generics you finished a Box<T> with isEmpty and getOrElse, a box that may hold nothing. And Optional has appeared already: in streams, findFirst() returned one and orElse took the value out. The stream flatMap from Collectors and stream recipes comes back near the end.When a method finds nothing#
Here is a small library catalog. findById walks the list of books and returns the one whose id matches. When no book matches, the loop ends without returning, and the method returns null, the value that means “no object”. Run it and read the error.
import java.util.List; public class Main { public static void main(String[] args) { Library library = new Library(); System.out.println(library.findById(2).title()); System.out.println(library.findById(7).title()); }} class Library { private final List<Book> books = List.of( new Book(1, "Emma"), new Book(2, "Dune"), new Book(3, "Beloved")); Book findById(int id) { for (Book book : books) { if (book.id() == id) { return book; } } return null; }} record Book(int id, String title) {}NullPointerException: Cannot invoke "Book.title()" because the return value of "Library.findById(int)" is null
A variable held null, so there was no object to use.
The first lookup prints Dune. The second stops the program on line 8 with a NullPointerException and this message:
Cannot invoke "Book.title()" because the return value of "Library.findById(int)" is null
Read it from the end. findById(7) returned null, and the program then asked that null for its title(). There was no book to ask, so Java stopped. Reading errors and debugging takes messages like this one apart piece by piece.
A search that finds nothing has still done its job. The problem is that the line Book findById(int id) gives no hint that it can happen. The return type says Book, so anyone who calls the method without reading its body expects a book. The compiler cannot warn them either, because to the compiler null is an acceptable value for any Book variable. Every caller has to remember to check for null, every time, and one forgotten check is a crash, often far from the method that returned the null.
Methods that work something out run into the same question. What is the longest word in a list? For an empty list there is no answer. Returning "" would claim there was a word with no letters in it, and returning null sets the same trap as findById.
Optional puts “maybe nothing” in the type#
Optional<T>, from the package java.util, is an object that holds either one value of type T or nothing at all. An Optional with a value inside is present; one with nothing inside is empty. Picture a small box with room for one thing. You always get the box, so you can always look inside it safely, and the label on the outside warns you that it may be empty.
Here is findById again, now returning an Optional<Book>. Two lines changed: it returns Optional.of(book) when it finds a match, and Optional.empty() when the loop ends without one.
import java.util.List;import java.util.Optional; public class Main { public static void main(String[] args) { Library library = new Library(); Optional<Book> found = library.findById(2); Optional<Book> missing = library.findById(7); System.out.println(found); System.out.println(missing); }} class Library { private final List<Book> books = List.of( new Book(1, "Emma"), new Book(2, "Dune"), new Book(3, "Beloved")); Optional<Book> findById(int id) { for (Book book : books) { if (book.id() == id) { return Optional.of(book); } } return Optional.empty(); }} record Book(int id, String title) {}It prints Optional[Book[id=2, title=Dune]] and then Optional.empty. Printing an Optional shows its value in square brackets, or Optional.empty when there is none. The method never returns null now. Every caller gets an Optional object back, and an empty one answers questions like any other object.
The compiler now reminds you
Try the line that crashed before, library.findById(7).title(), against the new method. It does not compile:
import java.util.List;import java.util.Optional; public class Main { public static void main(String[] args) { Library library = new Library(); System.out.println(library.findById(7).title()); }} class Library { private final List<Book> books = List.of( new Book(1, "Emma"), new Book(2, "Dune"), new Book(3, "Beloved")); Optional<Book> findById(int id) { for (Book book : books) { if (book.id() == id) { return Optional.of(book); } } return Optional.empty(); }} record Book(int id, String title) {}$ javac Main.javaMain.java:8: error: cannot find symbol System.out.println(library.findById(7).title()); ^ symbol: method title() location: class Optional<Book>1 errorThe compiler looked for a method title() on the type that findById returns, Optional<Book>, and found none. title() belongs to Book, and the book is inside the box. To reach it you call one of the methods on this page, and almost all of them make you decide, right there, what happens when the box is empty. The crash from the last section has become a compile error, which you see before the program ever runs.
Key ideaAn
Optionalreturn type tells every caller, in the type itself, that there may be no result. The compiler makes them open the box before they can use the value, and that is the moment to decide what happens when it is empty.
String? may be null and a String may not. Rust has Option and Haskell has Maybe. In Python's type hints, Optional[str] means “a str or None”, a note for checking tools. Java's Optional is a plain class: the compiler checks how you use it, but it cannot stop a method from returning null where an Optional was promised.Making an Optional: of, empty and ofNullable
Optional has no public constructor. You make one with one of three static methods:
Optional.of(value)wraps a value that is notnull.Optional.empty()gives an emptyOptional.Optional.ofNullable(value)wraps a value that might benull. It gives an emptyOptionalfornulland a present one for anything else.
ofNullable is the bridge from older methods that answer with null, such as a map's get. Here the map loans records who has borrowed which book.
import java.util.HashMap;import java.util.Map;import java.util.Optional; public class Main { public static void main(String[] args) { Map<Integer, String> loans = new HashMap<>(); loans.put(2, "Grace"); loans.put(3, "Alan"); Optional<String> title = Optional.of("Dune"); Optional<String> none = Optional.empty(); Optional<String> borrower2 = Optional.ofNullable(loans.get(2)); Optional<String> borrower1 = Optional.ofNullable(loans.get(1)); System.out.println(title + " " + none); System.out.println(borrower2 + " " + borrower1); Optional<String> broken = Optional.of(loans.get(1)); System.out.println(broken); }}NullPointerException
A variable held null, so there was no object to use.
The first two lines print Optional[Dune] Optional.empty and Optional[Grace] Optional.empty. Nobody has borrowed book 1, so loans.get(1) returned null, and ofNullable turned that null into an empty Optional. Line 19 passes the same null to Optional.of, which refuses it straight away: the program stops with a java.lang.NullPointerException that has no message.
Optional.of for a value you know is there, such as a book you have just found in a list. If you are wrong, it fails on that very line, where the mistake is easy to find. Use ofNullable for a value from code that may answer null, and empty()to say “nothing found”.loans.get(1) returns null. What does Optional.of(loans.get(1)) do?Writing a method that may find nothing
A method like this has the same shape every time: return Optional.of(...) at the point where you find the answer, and Optional.empty() at the end, once the search has failed. Here is one to put back together.
firstLongWord returns the first word with more than five letters, or an empty Optional if there is none. Put the lines in order so that the program prints Optional[giraffe] and then Optional.empty.Tap a line, then tap where it should go. Or use the arrows.
Getting the value out#
An Optional is only useful once you take the value out, and the methods for that differ in what they do when there is no value. The plainest way asks first.
Asking first: isPresent, isEmpty and get
isPresent() returns true when there is a value, and isEmpty(), added in Java 11, returns true when there is not. get() returns the value, and throws a NoSuchElementException when there is none.
import java.util.List;import java.util.Optional; public class Main { public static void main(String[] args) { Library library = new Library(); Optional<Book> found = library.findById(2); if (found.isPresent()) { System.out.println("Found " + found.get().title()); } Optional<Book> missing = library.findById(7); System.out.println(missing.isPresent() + " " + missing.isEmpty()); System.out.println(missing.get().title()); }} class Library { private final List<Book> books = List.of( new Book(1, "Emma"), new Book(2, "Dune"), new Book(3, "Beloved")); Optional<Book> findById(int id) { for (Book book : books) { if (book.id() == id) { return Optional.of(book); } } return Optional.empty(); }} record Book(int id, String title) {}NoSuchElementException: No value present
There was nothing left to take.
It prints Found Dune and false true, and then line 15 stops the program with java.util.NoSuchElementException: No value present. Calling get() on an empty Optional crashes just as surely as calling title() on null.
So asking first only moves the old null check: if (found.isPresent()) instead of if (book != null). It works, and you will see it in real code. The methods in the rest of this section are shorter, and they have no get() that you could call without the check.
A stand-in value: orElse
orElse(other) returns the value if there is one, and other if there is not. It suits a missing value that has a sensible stand-in. The library has a second lookup, borrowerOf, which tells you who has a book. Most books are on the shelf, so it returns an Optional<String>: the borrower's name, or empty.
import java.util.Map;import java.util.Optional; public class Main { public static void main(String[] args) { Library library = new Library(); String who = library.borrowerOf(2).orElse("nobody"); System.out.println("Book 2 is with " + who); who = library.borrowerOf(1).orElse("nobody"); System.out.println("Book 1 is with " + who); }} class Library { private final Map<Integer, String> loans = Map.of(2, "Grace", 3, "Alan"); Optional<String> borrowerOf(int bookId) { return Optional.ofNullable(loans.get(bookId)); }}It prints Book 2 is with Grace and Book 1 is with nobody. Notice that who is a plain String. orElse takes the value out of the box, so the rest of the code never sees an Optional.
A stand-in made only when needed: orElseGet
Sometimes the stand-in costs something to make: reading a file, asking a server, or searching old records. In the next program that work is checkPaperRecords(), which prints a line so that you can see when it runs. Decide what the program prints before you run it.
import java.util.Map;import java.util.Optional; public class Main { public static void main(String[] args) { Library library = new Library(); System.out.println(library.borrowerOf(2).orElse(checkPaperRecords())); System.out.println(library.borrowerOf(2).orElseGet(() -> checkPaperRecords())); System.out.println(library.borrowerOf(1).orElseGet(() -> checkPaperRecords())); } static String checkPaperRecords() { System.out.println("(checking the paper records)"); return "unknown"; }} class Library { private final Map<Integer, String> loans = Map.of(2, "Grace", 3, "Alan"); Optional<String> borrowerOf(int bookId) { return Optional.ofNullable(loans.get(bookId)); }}Which output does this program produce?
It is the second one. On line 8, checkPaperRecords() runs even though book 2 has a borrower. Java works out every argument of a method before the method starts, so it calls checkPaperRecords() to get the argument for orElse, and only then does orElse look in the box, find Grace and throw "unknown" away. orElse receives a finished value, so it has no way to skip making it.
orElseGet takes a Supplier instead. Supplier<T>, from java.util.function, is a functional interface whose one method, get(), takes nothing and returns a T. A lambda that takes nothing starts with empty parentheses: () -> checkPaperRecords(). orElseGet calls the lambda only when the box is empty, which is why the records are checked for book 1 on line 10 and not for book 2 on line 9.
Key idea
orElsealways works out its argument, even when the value is there. When the stand-in takes real work to make, useorElseGetwith a lambda, which runs only for an emptyOptional.
"nobody", 0 or an empty list, orElse is simpler and costs nothing extra. Switch to orElseGet when making the stand-in does work or has a side effect, such as printing or reading a file.No value is a mistake: orElseThrow
Sometimes a missing value means something has gone wrong. An id that came from your own list of books should always be found. orElseThrow(), added in Java 10, returns the value or throws a NoSuchElementException with the message No value present. It does exactly what get() does, but its name tells the reader that it can throw, and the documentation of get() names orElseThrow() as the method to prefer.
To throw an exception of your own, with a message that helps, pass orElseThrow a lambda that makes the exception. The lambda runs only when the box is empty. From here on, findById is a stream pipeline, because findFirst() already returns the Optional the method needs.
import java.util.List;import java.util.NoSuchElementException;import java.util.Optional; public class Main { public static void main(String[] args) { Library library = new Library(); Book dune = library.findById(2).orElseThrow(); System.out.println("Reading " + dune.title()); try { Book lost = library.findById(7).orElseThrow(); System.out.println("Reading " + lost.title()); } catch (NoSuchElementException e) { System.out.println("Caught: " + e.getMessage()); } Book book = library.findById(7) .orElseThrow(() -> new IllegalArgumentException("No book has id 7")); System.out.println("Reading " + book.title()); }} class Library { private final List<Book> books = List.of( new Book(1, "Emma"), new Book(2, "Dune"), new Book(3, "Beloved")); Optional<Book> findById(int id) { return books.stream() .filter(book -> book.id() == id) .findFirst(); }} record Book(int id, String title) {}IllegalArgumentException: No book has id 7
A method was given a value it does not accept.
It prints Reading Dune, and then Caught: No value present from the catch block. The last lookup throws the exception that the lambda made, and the program stops with java.lang.IllegalArgumentException: No book has id 7. That message names the missing id, at the place where the lookup failed, which is much easier to trace than a NullPointerException several methods later.
Optional really is a bug that should stop the program. In every other case, use orElse, orElseGet or the methods in the next section. When you do want the exception, write orElseThrow(), so that a reader can see it from the name.| Method | With a value | When empty |
|---|---|---|
isPresent() | true | false |
isEmpty() | false | true |
get() | the value | throws NoSuchElementException |
orElse(other) | the value | other |
orElseGet(supplier) | the value | whatever the supplier returns |
orElseThrow() | the value | throws NoSuchElementException |
orElseThrow(supplier) | the value | throws the exception the supplier makes |
Working inside the box: map, filter and flatMap#
Often what you need is something made from the value, such as the title of the book in the box. map(function) makes it without opening the box. If there is a value, map passes it to the function and puts the result in a new Optional. If the box is empty, map never calls the function and hands back an empty Optional.
import java.util.List;import java.util.Optional; public class Main { public static void main(String[] args) { Library library = new Library(); Optional<String> title = library.findById(2).map(Book::title); Optional<String> noTitle = library.findById(7).map(Book::title); System.out.println(title + " " + noTitle); String label = library.findById(7) .map(book -> book.title().toUpperCase()) .orElse("NO SUCH BOOK"); System.out.println(label); }} class Library { private final List<Book> books = List.of( new Book(1, "Emma"), new Book(2, "Dune"), new Book(3, "Beloved")); Optional<Book> findById(int id) { return books.stream() .filter(book -> book.id() == id) .findFirst(); }} record Book(int id, String title) {}It prints Optional[Dune] Optional.empty, and then NO SUCH BOOK. Step through lines 12 to 14 and watch the narration: findById(7) returns an empty Optional, and the lambda inside map never runs. The chain reads like a sentence, with no if for the empty case: find book 7, turn it into its title in capitals, or else use NO SUCH BOOK.
filter: keep the value only if it passes a test
filter(test) keeps the value when the test returns true for it, and gives back an empty Optional when the test returns false. An empty Optional has nothing to test, so it stays empty.
One case catches people out. A shopper types three spaces into the box for a discount code. Predict what the program prints.
import java.util.Optional; public class Main { public static void main(String[] args) { Optional<String> code = Optional.of(" "); Optional<String> trimmed = code.map(c -> c.trim()); Optional<String> real = trimmed.filter(c -> !c.isEmpty()); System.out.println(code.isPresent()); System.out.println(trimmed.isPresent()); System.out.println(real.isPresent()); }}Which output does this program produce?
It prints true, true and false. trim() turns " " into "", the empty string, and map puts that in a new box. A box that holds the empty string is present, because the empty string is still a value. Only filter, whose test fails for "", gives back an empty Optional. Printed, trimmed would show Optional[] and real would show Optional.empty.
Watch one value travel down a chain
The lab runs a greetingmethod for three users. It looks up the user's nickname, trims the spaces off it, drops it if nothing is left, and builds a greeting, with "Hello, guest" as the stand-in. Pick a user and press Next step to run one step at a time. Watch which lambdas run, and where an empty box makes the later steps skip theirs.
Ada's nickname has spaces around it.
return findNickname(user).map(name -> name.trim()).filter(name -> !name.isEmpty()).map(name -> "Hello, " + name).orElse("Hello, guest");
- lambdas run
- 0 of 3
- lambdas skipped
- 0
Nothing has run yet. Press Next step to call greeting("ada"), which starts with findNickname.
Here is the same method in a program. Step through it and follow the narration: for cleo, none of the three lambdas runs.
import java.util.Map;import java.util.Optional; public class Main { static Map<String, String> nicknames = Map.of("ada", " Ada ", "ben", " "); static Optional<String> findNickname(String user) { return Optional.ofNullable(nicknames.get(user)); } static String greeting(String user) { return findNickname(user) .map(name -> name.trim()) .filter(name -> !name.isEmpty()) .map(name -> "Hello, " + name) .orElse("Hello, guest"); } public static void main(String[] args) { System.out.println(greeting("ada")); System.out.println(greeting("ben")); System.out.println(greeting("cleo")); }}It prints Hello, Ada and then Hello, guesttwice. Ben and Cleo get the same greeting for different reasons: Ben's box was emptied by filter, while Cleo's was empty from the start.
map and filter work like the stream operations with the same names, on a stream that holds at most one element. Optional and streams both arrived in Java 8, and you have already seen stream methods such as findFirst() hand you an Optional.Key ideaOnce an
Optionalis empty, everymapandfilterafter it is skipped. The empty box travels to the end of the chain, whereorElseor one of its relatives decides what to do.
flatMap: when the function returns an Optional too
Now ask a question with two lookups in it: who has the book called Dune? First find the book by its title, which may find nothing. Then look up its borrower, which may also find nothing. Both lookups return an Optional. Here is what happens when you join them with map, and then with flatMap.
import java.util.List;import java.util.Map;import java.util.Optional; public class Main { public static void main(String[] args) { Library library = new Library(); Optional<Optional<String>> nested = library.findByTitle("Dune") .map(book -> library.borrowerOf(book.id())); System.out.println(nested); for (String title : List.of("Dune", "Emma", "Ulysses")) { Optional<String> who = library.findByTitle(title) .flatMap(book -> library.borrowerOf(book.id())); System.out.println(title + ": " + who); } }} class Library { private final List<Book> books = List.of( new Book(1, "Emma"), new Book(2, "Dune"), new Book(3, "Beloved")); private final Map<Integer, String> loans = Map.of(2, "Grace", 3, "Alan"); Optional<Book> findByTitle(String title) { return books.stream() .filter(book -> book.title().equals(title)) .findFirst(); } Optional<String> borrowerOf(int bookId) { return Optional.ofNullable(loans.get(bookId)); }} record Book(int id, String title) {}The first line prints Optional[Optional[Grace]], a box inside a box. The function passed to map already returns an Optional<String>, and map wraps whatever its function returns in another Optional, so the result is an Optional<Optional<String>>. To get Grace out of that you would have to open two boxes.
flatMap is made for this case. It expects a function that returns an Optional, and hands that Optional back as it is, without wrapping it again. The loop prints Dune: Optional[Grace], Emma: Optional.empty and Ulysses: Optional.empty. Emma is in the catalog but nobody has borrowed it; Ulysses is not in the catalog at all. Both come out empty, because flatMap gives back an empty Optional as soon as either lookup finds nothing. When your program has to tell those two cases apart, do the lookups one at a time.
f that returns a plain value, such as a String.f that returns an Optional, such as an Optional<String>.f's result in a new Optional.f's Optional as it is.f returns null, the result is an empty Optional.f must never return null: flatMap throws a NullPointerException if it does.findById returns an Optional<Book> and borrowerOf returns an Optional<String>. What is the type of library.findById(2).map(book -> library.borrowerOf(book.id()))?firstLongerThan so that it returns the first song in the list that is longer than seconds, or an empty Optional if no song is. main is already written and should print Intro, Night Drive and none.Acting on the value: ifPresent, or and stream#
ifPresent and ifPresentOrElse
Sometimes there is nothing to return, only something to do with the value: print it, add it to a list, send it somewhere. ifPresent(action) runs the action with the value if there is one, and does nothing for an empty Optional. ifPresentOrElse(action, emptyAction), from Java 9, takes a second action for the empty case.
import java.util.List;import java.util.Optional; public class Main { public static void main(String[] args) { Library library = new Library(); library.findById(2) .ifPresent(book -> System.out.println("Reserved " + book.title())); library.findById(7) .ifPresent(book -> System.out.println("Reserved " + book.title())); for (int id : List.of(3, 9)) { library.findById(id).ifPresentOrElse( book -> System.out.println("Reserved " + book.title()), () -> System.out.println("No book has id " + id)); } }} class Library { private final List<Book> books = List.of( new Book(1, "Emma"), new Book(2, "Dune"), new Book(3, "Beloved")); Optional<Book> findById(int id) { return books.stream() .filter(book -> book.id() == id) .findFirst(); }} record Book(int id, String title) {}It prints Reserved Dune, then Reserved Beloved and No book has id 9. The lookup of book 7 on lines 10 and 11 prints nothing at all. The second argument of ifPresentOrElse is a Runnable, a functional interface whose one method, run(), takes nothing and returns nothing, so its lambda starts with () as well. You will meet Runnable again in introduction to threads.
or: a second place to look
or(supplier), also from Java 9, keeps an Optional that has a value. For an empty one it calls the supplier, which returns another Optional to use instead. Unlike orElse, it gives you an Optional back, so you can try a third place or carry on with map. Here a town library falls back on the school library's catalog.
import java.util.List;import java.util.Optional; public class Main { public static void main(String[] args) { Library town = new Library(List.of( new Book(1, "Emma"), new Book(2, "Dune"))); Library school = new Library(List.of( new Book(3, "Beloved"), new Book(4, "Kindred"))); for (int id : List.of(2, 4, 9)) { Optional<Book> found = town.findById(id) .or(() -> school.findById(id)); System.out.println(id + ": " + found); } }} class Library { private final List<Book> books; Library(List<Book> books) { this.books = books; } Optional<Book> findById(int id) { return books.stream() .filter(book -> book.id() == id) .findFirst(); }} record Book(int id, String title) {}It prints 2: Optional[Book[id=2, title=Dune]], 4: Optional[Book[id=4, title=Kindred]] and 9: Optional.empty. Book 2 is in the town library, so the school's catalog is never searched. Book 4 comes from the fallback, and book 9 is in neither.
stream(): zero elements or one
stream(), from Java 9 as well, turns an Optional into a stream with one element, or with none when the Optional is empty. It is handy when you have a stream of Optionals: together with the flatMap from Collectors and stream recipes, it drops the empty ones and keeps the values.
import java.util.List;import java.util.Optional; public class Main { public static void main(String[] args) { Library library = new Library(); System.out.println(library.findById(2).stream().toList()); System.out.println(library.findById(7).stream().toList()); List<Integer> wanted = List.of(3, 7, 1, 9); List<Book> onShelf = wanted.stream() .map(id -> library.findById(id)) .flatMap(Optional::stream) .toList(); System.out.println(onShelf); }} class Library { private final List<Book> books = List.of( new Book(1, "Emma"), new Book(2, "Dune"), new Book(3, "Beloved")); Optional<Book> findById(int id) { return books.stream() .filter(book -> book.id() == id) .findFirst(); }} record Book(int id, String title) {}The first two lines print [Book[id=2, title=Dune]] and []. In the pipeline, map turns each id into an Optional<Book>, and flatMap(Optional::stream) replaces each one with its stream of zero books or one. The ids with no book drop out, and the last line is [Book[id=3, title=Beloved], Book[id=1, title=Emma]], in the order the ids were asked for.
Optionals the library hands you#
You will write methods that return an Optional, and you will call even more of them. Every stream operation that may have no answer returns one.
| Method | Called on | Returns |
|---|---|---|
findFirst(), findAny() | Stream<T> | Optional<T> |
max(comparator), min(comparator) | Stream<T> | Optional<T> |
reduce(operator), with no starting value | Stream<T> | Optional<T> |
max(), min() | IntStream | OptionalInt |
average() | IntStream | OptionalDouble |
The longest word in a list, the method with no good answer at the start of this lesson, takes one line with max. It compares the elements with a Comparator, here one that compares words by length as in Comparable, Comparator and sorting, and returns the biggest in an Optional.
import java.util.Arrays;import java.util.Comparator;import java.util.List;import java.util.Optional;import java.util.OptionalDouble;import java.util.OptionalInt; public class Main { public static void main(String[] args) { Comparator<String> byLength = Comparator.comparingInt(String::length); List<String> words = List.of("pear", "banana", "fig"); List<String> noWords = List.of(); Optional<String> longest = words.stream().max(byLength); Optional<String> shortest = words.stream().min(byLength); System.out.println(longest + " " + shortest); System.out.println(noWords.stream().max(byLength)); int[] scores = {72, 95, 43}; OptionalInt top = Arrays.stream(scores).max(); OptionalDouble mean = Arrays.stream(scores).average(); System.out.println(top + " " + mean); System.out.println(top.getAsInt() + " " + mean.getAsDouble()); int[] noScores = {}; OptionalInt noTop = Arrays.stream(noScores).max(); OptionalDouble noMean = Arrays.stream(noScores).average(); System.out.println(noTop + " " + noMean); System.out.println(noMean.orElse(0)); System.out.println(noTop.getAsInt()); }}NoSuchElementException: No value present
There was nothing left to take.
It prints Optional[banana] Optional[fig], then Optional.empty for the list with no words. For the scores it prints OptionalInt[95] OptionalDouble[70.0] and 95 70.0. For the empty array it prints OptionalInt.empty OptionalDouble.empty and 0.0, and the last line throws java.util.NoSuchElementException: No value present.
OptionalInt, OptionalLong and OptionalDouble
The number streams return their own kinds of Optional, which hold a plain int, long or double without boxing it. The method that takes the value out has the type in its name: getAsInt(), getAsLong() and getAsDouble(). Like get(), each one throws a NoSuchElementException when there is no value. They also have isPresent, isEmpty, orElse, orElseGet, orElseThrow, ifPresent, ifPresentOrElse and stream. They have no map, filter or flatMap. They have no ofNullable either, because a plain int can never be null.
printTop so that it prints Top score: 95 for the first array and No scores yet for the empty one. Use Arrays.stream(scores).max(), which returns an OptionalInt, and ifPresentOrElse.Using Optional well#
The Java documentation says that Optionalis meant mainly as a method return type, for when there is a clear need to say “no result” and null would be likely to cause errors. These habits follow from that:
- Return an
Optionalfrom a method that may find nothing, such as a lookup or a search. When a missing value can only mean that the caller made a mistake, throwing an exception straight away is clearer. - Never return
nullfrom a method that returns anOptional, and never set anOptionalvariable tonull. The documentation says such a variable should always point at anOptionalobject. An emptyOptionalis how you say “nothing”. - Never call
get()without a check. PreferorElse,orElseGet,mapandifPresent, and when an emptyOptionalshould stop the program, writeorElseThrow(), whose name says so. - Use
Optional.ofonly for values that cannot benull, because it throws onnull. UseofNullablefor values that might be. - Do not wrap a value only to test it.
Optional.ofNullable(name).isPresent()is a longer way to writename != null. - Compare two
Optionals withequals, never with==. The documentation does not promise that every emptyOptionalis the same object. - Keep
Optionalout of fields, method parameters and collections.
Fields, parameters and collections
The Optional tutorial on dev.java lists fields, method parameters, collections and maps as places where an Optional does not belong. Each one has a simpler alternative.
Optional<String> nickname;String nickname; that may be null inside the class, and a method Optional<String> nickname() that wraps it with ofNullable for callers.greet(Optional<String> name)greet() and greet(String name). A caller could still pass null for an Optional parameter, which leaves three cases to handle instead of two.List<Optional<Book>>List<Book> with the missing books left out.Optional<List<Book>>, or null, for “no results”List<Book>. An empty list already says “none”.Optional the way it was designed to be used?Key ideaUse
Optionalas the return type of a method that may find nothing, and almost nowhere else. AnOptionalitself should never benull.
Try it
Answer each one in your head or on paper first, then open the answer.
String label with an if and an else: when library.findById(id) has a book, label is its title in capitals, and otherwise it is MISSING. The if calls found.get() after checking found.isPresent(). Write the same thing as one chain with map and orElse, then open the program to compare the two.settings.find("theme").orElse(loadDefaultTheme()), where find returns an Optional<String> and loadDefaultTheme() reads a file. Almost every user has set a theme, yet the file is read every time. Why, and how do you fix it?true and then stops with a NullPointerException on line 9. Find the bug and fix it.max, map and orElse to find the name of the student with the highest score, with nobody as the stand-in for an empty list. What does it print?Optional.Practice
Work through these on your own. Each one builds on this page.
static Optional<Integer> parseAge(String text). It returns the age for text such as "42", and an empty Optional when the text is not a whole number or the age is below 0 or above 150. Print parseAge("42"), parseAge("forty") and parseAge("200").Player kept its best score in an Integer field that stayed null until the first game. Keep that field, and add a method Optional<Integer> best() that wraps it. Then print player.best().map(s -> "Best: " + s).orElse("No games yet") before the first game and after a few games.Optional<String> define(String word), which looks in the first map and, if the word is not there, in the second. Print the meaning of a word from each map and of a word that is in neither, with orElse("unknown word").-1 when it does not find the target. Rewrite it to return an OptionalInt, using OptionalInt.of(i) and OptionalInt.empty(), and change the caller to print found at index 3 or not found. In two sentences, say what the new return type tells a caller that -1 did not.Further reading#
Spotted a mistake, or a part that did not make sense? Report it by email