Optional: Кот Шрёдингера в Java 8
Представим, что в коробке находятся кот, радиоактивное вещество и колба с синильной кислотой. Вещества так мало, что в течение часа может распасться только один атом. Если в течение часа он распадётся, считыватель разрядится, сработает реле, которое приведёт в действие молоток, который разобьёт колбу, и коту настанет карачун. Поскольку атом может распасться, а может и не распасться, мы не знаем, жив ли кот или уже нет, поэтому он одновременно и жив, и мёртв. Таков мысленный эксперимент, именуемый «Кот Шрёдингера».
Класс Optional обладает схожими свойствами — при написании кода разработчик часто не может знать — будет ли существовать нужный объект на момент исполнения программы или нет, и в таких случаях приходится делать проверки на null. Если такими проверками пренебречь, то рано или поздно (обычно рано) Ваша программа рухнет с NullPointerException.
Коллеги! Статья, как и любая другая, не идеальна и может быть поправлена. Если Вы видите возможность существенного улучшения данного материала, укажите её в комментариях.
Как получить объект через Optional?
Как уже было сказано, класс Optional может содержать объект, а может содержать null. К примеру, попытаемся извлечь из репозитория юзера с заданным ID:
Возможно, юзер по такому ID есть в репозитории, а возможно, нет. Если такого юзера нет, к нам в стектрейс прилетает NullPointerException. Не имей мы в запасе класса Optional, нам пришлось бы изобретать какую-нибудь такую конструкцию:
Согласитесь, не очень. Намного приятнее иметь дело с такой строчкой:
Мы получаем объект, в котором может быть запрашиваемый объект — а может быть null. Но с Optional надо как-то работать дальше, нам нужна сущность, которую он содержит (или не содержит).
Cуществует всего три категории Optional:
- Optional.of — возвращает Optional-объект.
- Optional.ofNullable -возвращает Optional-объект, а если нет дженерик-объекта, возвращает пустой Optional-объект.
- Optional.empty — возвращает пустой Optional-объект.
Метод позволяет выполнить какое-то действие, если объект не пустой.
Если обычно мы выполняем какое-то действие в том случае, когда объект отсутствует (об этом ниже), то здесь как раз наоборот.
.isPresent()Этот метод возвращает ответ, существует ли искомый объект или нет, в виде Boolean:
Если Вы решили использовать нижеописанный метод get(), то не будет лишним проверить, существует ли данный объект, при помощи этого метода, например:
Но такая конструкция лично мне кажется громоздкой, и о более удобных методах получения объекта мы поговорим ниже.
Как получить объект, содержащийся в Optional?
Существует три прямых метода дальнейшего получения объекта семейства orElse(); Как следует из перевода, эти методы срабатывают в том случае, если объекта в полученном Optional не нашлось.
- orElse() — возвращает объект по дефолту.
- orElseGet() — вызывает указанный метод.
- orElseThrow() — выбрасывает исключение.
Подходит для случаев, когда нам обязательно нужно получить объект, пусть даже и пустой. Код, в таком случае, может выглядеть так:
Эта конструкция гарантированно вернёт нам объект класса User. Она очень выручает на начальных этапах познания Optional, а также, во многих случаях, связанных с использованием Spring Data JPA (там большинство классов семейства find возвращает именно Optional).
.orElseThrow()Очень часто, и опять же, в случае с использованием Spring Data JPA, нам требуется явно заявить, что такого объекта нет, например, когда речь идёт о сущности в репозитории. В таком случае, мы можем получить объект или, если его нет, выбросить исключение:
Если сущность не обнаружена и объект null, будет выброшено исключение NoEntityException (в моём случае, кастомное). В моём случае, на клиент уходит строчка «Пользователь не найден. Проверьте данные запроса».
.orElseGet()Если объект не найден, Optional оставляет пространство для «Варианта Б» — Вы можете выполнить другой метод, например:
Если объект не был найден, предлагается поискать в другом месте.
Этот метод, как и orElseThrow(), использует Supplier. Также, через этот метод можно, опять же, вызвать объект по умолчанию, как и в .orElse():
Помимо методов получения объектов, существует богатый инструментарий преобразования объекта, морально унаследованный от stream().
Работа с полученным объектом.
Как я писал выше, у Optional имеется неплохой инструментарий преобразования полученного объекта, а именно:
- get() — возвращает объект, если он есть.
- map() — преобразовывает объект в другой объект.
- filter() — фильтрует содержащиеся объекты по предикату.
- flatmap() — возвращает множество в виде стрима.
Метод get() возвращает объект, запакованный в Optional. Например:
Будет получен объект User, запакованный в Optional. Такая конструкция крайне опасна, поскольку минует проверку на null и лишает смысла само использование Optional, поскольку Вы можете получить желаемый объект, а можете получить NPE. Такую конструкцию придётся оборачивать в .isPresent().
Этот метод полностью повторяет аналогичный метод для stream(), но срабатывает только в том случае, если в Optional есть не-нулловый объект.
В примере мы получили одно из полей класса User, упакованного в Optional.
.filter()Данный метод также позаимствован из stream() и фильтрует элементы по условию.
.flatMap()Этот метод делает ровно то же, что и стримовский, с той лишь разницей, что он работает только в том случае, если значение не null.