Optional
Метод поиска не всегда что-то находит. Если в таком случае вернуть null, вызывающий код легко забудет проверку и упадёт с NullPointerException. Optional — это контейнер, который либо содержит значение, либо пуст, и сам тип напоминает: «результата может не быть».
Создание Optional
Optional.of() оборачивает существующее значение, Optional.empty() создаёт пустой контейнер. Методы isPresent() и isEmpty() проверяют, есть ли внутри значение:
void main() {
Optional<String> name = Optional.of("Анна");
Optional<String> nothing = Optional.empty();
IO.println(name.isPresent());
IO.println(nothing.isEmpty());
IO.println(name);
IO.println(nothing);
}Результат:
true
true
Optional[Анна]
Optional.emptyМетод, который может не найти
Главное применение Optional — тип результата метода поиска. Если значение может оказаться null, его оборачивают через Optional.ofNullable():
Map<String, String> phones = Map.of("Анна", "+49 30 1234567");
Optional<String> findPhone(String name) {
return Optional.ofNullable(phones.get(name));
}
void main() {
IO.println(findPhone("Анна"));
IO.println(findPhone("Олег"));
}Результат:
Optional[+49 30 1234567]
Optional.emptyТот, кто вызывает findPhone(), видит по типу, что телефона может не быть, и обязан решить, что делать в этом случае.
Значение по умолчанию
orElse() возвращает значение из контейнера или запасное, если контейнер пуст. orElseGet() делает то же, но вычисляет запасное значение лямбдой — только когда оно действительно нужно:
void main() {
Optional<String> city = Optional.empty();
IO.println(city.orElse("Город не указан"));
IO.println(city.orElseGet(() -> "Берлин"));
}Результат:
Город не указан
БерлинПреобразование: map и filter
map() применяет функцию к значению, если оно есть, и возвращает новый Optional. filter() оставляет значение, только если оно подходит под условие. Пустой Optional проходит через них без изменений:
void main() {
Optional<String> email = Optional.of(" Anna@Mail.com ");
String result = email
.map(String::strip)
.map(String::toLowerCase)
.filter(e -> e.contains("@"))
.orElse("нет адреса");
IO.println(result);
}Результат:
anna@mail.comДействие, если значение есть
ifPresent() выполняет действие только при наличии значения, а ifPresentOrElse() позволяет описать и запасную ветку. orElseThrow() возвращает значение или выбрасывает исключение, если его нет:
void main() {
Optional<Integer> discount = Optional.of(10);
discount.ifPresent(d -> IO.println("Скидка " + d + "%"));
Optional<Integer> none = Optional.empty();
none.ifPresentOrElse(
d -> IO.println("Скидка " + d + "%"),
() -> IO.println("Скидки нет"));
}Результат:
Скидка 10%
Скидки нетЧастая ошибка
Метод get() возвращает значение, но у пустого Optional выбрасывает исключение. Вызывать его без проверки — та же ошибка, что и обращение к null:
void main() {
Optional<String> phone = Optional.empty();
IO.println(phone.get());
}Результат:
Exception in thread "main" java.util.NoSuchElementException: No value presentВместо get() используйте orElse(), ifPresent() или orElseThrow() с понятным сообщением. И не применяйте Optional для полей и параметров — он задуман как тип результата метода.