Источник: Medium Благодаря этой статье вы сможете лучше понять предназначение Optional при работе с кодом Java. Кофе-брейк #161. Как обрабатывать Null в Java с помощью Optional - 1Когда я впервые начал работать с Java-кодом, мне часто советовали использовать Optional. Но я в то время плохо понимал, почему использование Optional лучше, чем реализации обработки для значений null. В этой статье я хочу поделиться с вами тем, зачем, по моему мнению, нам всем стоит больше использовать Optional, и как избежать чрезмерной “оптионализации” кода, которое вредит его качеству.

Что такое 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.

Учтите, что если вы используете только get() и isPresent() при работе с Optional, вы многое упускаете! Чтобы это понять, давайте сейчас перепишем приведенный выше пример с Optional.

Улучшение проверки 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().

Метод вернет значение, содержащееся в экземпляре 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 без значений.

Заключение

Optional стал мощным дополнением к библиотеке Java. Он дает способ обработки объектов, которые могут не существовать. Поэтому его следует учитывать при разработке методов, но не попадая в ловушку злоупотребления. Да, вы можете написать отличный код, который реализует проверки null и обработку null, но сообщество Java предпочитает использовать Optional. Он эффективно сообщает, как обрабатывать отсутствующие значения, его намного легче читать, чем беспорядочные проверки Null, и в долгосрочной перспективе это приводит к меньшему количеству ошибок в коде.