Что такое Optional?
Параметр Optional используется для переноса объектов и обеспечения обработки ссылок на null с помощью различных API-интерфейсов. Давайте посмотрим на отрывок кода:
Coffee coffee = new Coffee();
Integer quantity = coffee.getSugar().getQuantity();
У нас есть экземпляр Coffee, в котором мы получаем некоторое количество sugar из экземпляра объекта Sugar. Если мы предположим, что значение количества никогда не устанавливалось в конструкторе Coffee, то coffee.getSugar().getQuantity() вернет исключение NullPointerException.
Конечно, мы всегда можем использовать старые добрые проверки null, чтобы исправить проблему.
Coffee coffee = new Coffee();
Integer quantity = 0;
if (coffee.getSugar() != null) {
quantity = coffee.getSugar().getQuantity();
}
Теперь вроде бы все нормально. Но при написании Java-кода нам лучше избегать реализации проверок null. Давайте посмотрим, как это можно сделать, используя Optional.
Как создать Optional
Существует три способа создания объектов Optional:of(T value) — создание экземпляра Optional ненулевого (non-null) объекта. Имейте в виду, что использование of() для ссылки на объект null приведет к появлению NullPointerException.
ofNullable(T value) — создание значения Optional для объекта, который может быть нулевым (null).
empty() — создание экземпляра Optional, который представляет ссылку на null.
// пример использования Optional.of(T Value)
String name = "foo";
Optional<String> stringExample = Optional.of(name)
// пример использования Optional.ofNullable(T Value)
Integer age = null;
Optional<Integer> integerExample= Optional.ofNullable(age)
// пример использования Optional.empty()
Optional<Object> emptyExample = Optional.empty();
Итак, у вас есть объект Optional. А теперь давайте познакомимся с двумя основными методами для Optional:isPresent() — этот метод сообщает вам, содержит ли объект Optional ненулевое (non-null) значение.
get() — извлекает значение для Optional с текущим значением. Имейте в виду, что вызов get() для пустого Optional приведет к NullPointerException.
Улучшение проверки Null с помощью Optional
Итак, как нам улучшить приведенный выше код? С Optional мы можем понять наличие объекта с помощью isPresent() и получить его с помощью get(). Начнем с упаковки результата coffee.getSugar() с Optional и использованием метода isPresent(). Это поможет нам определить, возвращает ли getSugar() значение null.
Coffee coffee = new Coffee();
Optional<String> sugar = Optional.ofNullable(coffee.getSugar());
int quantity = 0;
if (sugar.isPresent()) {
Sugar sugar = sugar.get();
int quantity = sugar.getQuantity();
}
Глядя на этот пример, упаковка результата coffee.getSugar() в Optional, похоже, не прибавляет никакой ценности, а скорее добавляет хлопот.
Мы можем улучшить результат, используя то, что я считаю своими любимыми функциями из класса Optional:map(Function<? super T,? extends U> mapper) — преобразует значение, содержащееся в Optional, с предоставленной функцией. Если параметр Optional пуст, то map() вернет Optional.empty().
orElse(T other) — это “специальная” версия метода get(). Она может получить значение, содержащееся в Optional. Однако в случае пустого Optional это вернет значение, переданное в метод orElse().
Coffee coffee = new Coffee();
Integer quantity = Optional.ofNullable(coffee.getSugar())
.map(it -> it.getQuantity())
.orElse(0);
Это реально круто — по крайней мере я так думаю. Теперь, если в случае пустого значения мы не хотим возвращать значение по умолчанию, то нам нужно создать какое-то исключение.
orElseThrow(Supplier<? extends X> exceptionSupplier) возвращает значение, содержащееся в параметрах Optional, или выдает исключение в случае пустого Optional.
Coffee coffee = new Coffee();
Integer quantity = Optional.ofNullable(coffee.getSugar())
.map(it -> it.getQuantity())
.orElseThrow(IllegalArgumentException::new);
Как видите, Optional дает несколько преимуществ:- абстрагирует проверки null
- предоставляет API для обработки объектов null
- позволяет декларативному подходу выражать то, что достигается
Как стать эффективным с Optional
В своей работе я использую Optional как возвращаемый тип, когда метод может возвращать состояние “no result”. Обычно я использую его при определении возвращаемых типов для методов.
Optional<Coffee> findByName(String name) {
...
}
Иногда в этом нет необходимости. Например, если у меня есть метод, возвращающий int, например getQuantity() в классе Sugar, тогда метод может вернуть 0, если результат равен null, чтобы представить “no quantity” (нет количества).
Теперь, зная это, мы можем подумать, что параметр Sugar в классе Coffee может быть представлен как Optional. На первый взгляд это кажется хорошей идеей, поскольку теоретически сахар не обязательно должен присутствовать в кофе. Однако именно здесь я хотел бы остановиться на том, когда не следует использовать Optional.
Мы должны избегать использования Optional в следующих сценариях:В качестве типов параметров для POJO, таких как объекты DTO. Optional не сериализуемы, поэтому их использование в POJO лишает объект возможности сериализации.
В качестве аргумента метода. Если аргумент метода может быть null, то с точки зрения чистого кода передача null по-прежнему предпочтительнее, чем передача Optional. Кроме того, вы можете создать перегруженные методы для абстрактной обработки отсутствия аргумента нулевого метода.
Для представления объекта Collection, который отсутствует. Коллекции могут быть пустыми, поэтому пустая Collection, как пустой Set или List, должна использоваться для представления Collection без значений.
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ