JavaRush/Java блог/Random/Кофе-брейк #161. Как обрабатывать Null в Java с помощью O...

Кофе-брейк #161. Как обрабатывать Null в Java с помощью Optional

Статья из группы Random
участников
Источник: 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, и в долгосрочной перспективе это приводит к меньшему количеству ошибок в коде.
Комментарии (2)
  • популярные
  • новые
  • старые
Для того, чтобы оставить комментарий Вы должны авторизоваться
gjkjntyxbr
Уровень 35
23 сентября 2022, 12:24
Но при написании Java-кода нам лучше избегать реализации проверок null
А почему?
Justinian Judge в Mega City One Master
26 сентября 2022, 13:29
Хороший пример, почему всегда лучше читать в оригинале. В оригинале написано: "However, when writing Java code, we want to avoid having to implement such null checks. " Но, когда мы пишем джава код, мы хотим избегать реализации ПОДОБНЫХ проверок на налл. Почему подобных, потому что иначе мы бы проверки на налл писали каждые две строки, а это существенно усложнило бы читаемость и maintability, поддерживаемость, способность к рефакторингу кода, что для джава кода (в отличие к примеру от С/С++) очень существенный недостаток, поскольку код на джаве пишется с расчетом на частую изменяемость - ведь там мы в основном фиксируем бизнес-логику, которая меняется постоянно. Как конструкт управления проверками на налл и придумали Optional, для более удобной организации с переменными, которые могут содержать налл. Почему налл хуже, да потому что мы можем здесь проверить на налл, через 5 строк проверить на налл, а через 10 строк забыть, и выскочит наллпойнтер. Опшенал тоже может давать наллпойнтер, если использовать Optional.of на переменной, которая может быть наллом. но в целом это намного более безопасней инструмент и самое главное, как только мы в коде видим Optional мы сразу видим, что там может быть значение, а может и не быть, то есть 100% нужна обработка налл. Если мы видим просто переменную обычную, как в примере, для нас это ничего не говорит - может там налл быть или нет, нужна проверка на налл или нет. Вот это и имелось ввиду, что нельзя строить логику программы на многих проверках на налл, ведь есть риск пропустить , для кого-то это будет неочевидно и пропустит. Безусловно, много где проверка на налл будет использоваться, когда иначе нельзя, Опшенал пихать во все тоже моветон, но этот баланс, когда и что использовать приходит на практике и по мере знакомства с кодом других на проекте. Вообще, null в джаве как конструкция это холиварная тема, но идеальных языков не существует, мы используем язык как есть.