Optional
Вернёмся к концу конвейера. Иногда сборщику нужен не ящик со всем, что доехало, а одна коробка — первая, которая прошла все станции. Но что, если до конца не доехало ничего?
Лента могла бы отдать null. С null вы уже встречались: он выглядит как обычная ссылка, пока на нём не вызовут метод, и тогда программа падает с NullPointerException.
Поэтому лента отдаёт находку в прозрачном футляре. В футляре либо лежит коробка, либо он пустой, и это видно сразу. Прежде чем взять коробку, вы смотрите в футляр. В Java такой футляр называется 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
}
}Давайте разберём программу по строкам:
- В первом стриме
filterпропускает только слова длиннее четырёх букв, и до конца доезжаетzebra. findFirst()— терминальная операция: она берёт первый элемент, который доехал до конца, и кладёт его в футлярOptional<String>.- Во втором стриме слов длиннее девяти букв нет, и
findFirst()возвращает пустой футляр. orElse("none")отдаёт содержимое футляра, а если футляр пуст — запасное значение"none". Поэтому программа печатаетzebraиnone.
Футляр вместо null
Optional — объект-контейнер, в котором либо лежит одно значение, либо нет ничего. Метод, который может ничего не найти, возвращает Optional, и по его типу сразу видно: результата может не быть. Обычная ссылка такого предупреждения не даёт.
Если напечатать футляр целиком, println покажет Optional[zebra] для полного и Optional.empty для пустого. Так же, как findFirst, футляр возвращают max и min стрима: у пустого списка нет ни самого длинного, ни самого короткого слова.
Зачем нужен Optional, часто спрашивают на собеседовании. Ответ: чтобы отсутствие результата было видно по типу, а не пряталось в null.
Как достать значение
У футляра есть несколько способов отдать содержимое, и каждый по-своему поступает с пустым футляром.
orElse
orElse(x) возвращает значение из футляра, а для пустого — запасное значение x. Результат есть всегда, поэтому это самый спокойный способ.
isPresent и get
isPresent() отвечает, есть ли что-то в футляре, а get() достаёт значение. Вместе их пишут так: сначала проверка, потом get().
ifPresent
ifPresent принимает Consumer и выполняет его, только если значение есть. Для пустого футляра он не делает ничего.
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));Разберём по строкам:
found.isPresent()даётtrue, поэтомуget()безопасен и возвращает"zebra", а печать даёт5.found.ifPresent(...)печатаетfound zebra.missing.ifPresent(...)ничего не печатает: футляр пуст.
Здесь картинка с футляром неточна. В настоящий футляр можно положить другую коробку, а Optional не меняется: в нём навсегда то, что положил туда метод.
Частая ошибка
get() вызвали, не заглянув в футляр:
System.out.println(missing.get()); // ✗Exception in thread "main" java.util.NoSuchElementException: No value presentПрограмма падает: футляр пуст, и get() бросает исключение. Это та же беда, что с null, только под другим именем: футляр не помог, потому что в него не заглянули. Берите orElse или ifPresent, а get() вызывайте только после проверки isPresent().
Главное
Optional— футляр, в котором либо одно значение, либо ничего;findFirstвозвращает именно его.orElse(x)отдаёт значение или запасноеx, аifPresentвыполняет действие, только если значение есть.get()на пустомOptionalбросаетNoSuchElementException, поэтому без проверкиisPresent()его не вызывают.- Пустой результат в
Optionalвиден по типу, аnullпрячется до первого вызова метода.