Необязательная монада для улучшения нулевых проверок в вашем коде

NullPointerException. Исключение, с которым чаще всего сталкиваются Java-программисты в жизни. Обработка нулевых значений может вызывать стресс. Все эти if-elses разбросаны по всей вашей кодовой базе. К счастью, в большинстве случаев вы можете сократить свой шаблонный код с проверкой нуля, используя класс Optional.

Дополнительный API был впервые представлен как часть Java Development Kit 8 и улучшен в последующих версиях JDK. Это контейнер, который позволяет нам обрабатывать значение, которое может не существовать (монады!). Несмотря на то, что монады могут показаться пугающими, вам не нужно знать, что это значит, чтобы понять, как работает Optional API.

Это также не серебряная пуля, и она не решит всех нулевых проблем, которые есть в вашем коде. Вместо этого, при правильном использовании, он помогает нам создавать более надежный API и безошибочный код.

В этой статье мы поговорим о том, что можно и чего нельзя делать при его использовании.

Дополнительный API

Необязательный класс - это монадический контейнер, который может содержать или не содержать внутри себя значение. Вот и все.

По сути, Optional - это класс-оболочка, который содержит указатель на какой-то другой объект (присутствует) или ни на что (отсутствует). Другой способ взглянуть на это - как на способ предоставить решение на уровне типа для представления необязательных значений вместо нулевых ссылок.

Хотя может показаться разумным использовать его повсюду и избавиться от нулей, Optional API был разработан, чтобы помочь нам обрабатывать возвращаемые значения функции, как упоминал Брайан Гетц:

Наше намерение состояло в том, чтобы предоставить ограниченный механизм для типов, возвращаемых библиотечными методами, где должен был быть четкий способ представления отсутствия результата, а использование null для таких типов с большой вероятностью могло вызвать ошибки.

По этой причине не рекомендуется использовать его для удаления всего кода нулевой проверки, который есть в вашей программе (вы можете сделать это на свой страх и риск).

Семь необязательных правил

Поскольку теперь мы знаем, что Java Optional не следует использовать повсюду, есть некоторое правило, которому мы предлагаем следовать. Фактически, Стюарт Маркс определяет семь правил, которым нужно следовать при использовании Optionals [3]:

  1. Никогда и никогда не используйте null для необязательной переменной или возвращаемого значения;
  2. Никогда не используйте Optional.get (), пока не докажете, что Optional присутствует;
  3. Предпочитайте альтернативы Optional.isPresent () и Optional.get ();
  4. Как правило, создание и Optional для конкретной цели связывания методов для получения значения - плохая идея;
  5. Если в Необязательной цепочке есть вложенная Необязательная цепочка или промежуточный результат Необязательный ‹Необязательный ‹T››, это, вероятно, слишком сложно;
  6. Избегайте использования Optional в полях, параметрах методов и коллекциях;
  7. Избегайте использования чувствительных к идентичности операций на Optionals (например, сериализуемых).

Что можно и чего нельзя делать

Как и любой API, его можно использовать правильно - и неправильно. Вот несколько советов, которые следует учитывать при обработке нулевых значений с помощью Optionals:

Создание

Основная идея Optional - упростить обработку возвращаемых значений функций, поэтому следует создать экземпляр Optional при возврате значения, которое может существовать или не существовать:

Вы должны знать, что для значений, которые могут быть нулевыми, вы должны вызвать Optional::ofNullable вместо Optional::of, что вызовет NullPointerException [1].

Тем не менее, вы никогда не должны использовать Optional в качестве параметров функций и конструкторов, ни только ради объединения методов для последующего получения значения.

Необязательно как атрибуты класса

Кажется разумным использовать Optional, чтобы указать, что атрибут объекта может иметь значение NULL. Проблема здесь в том, что вы не должны этого делать. Накладные расходы сборщика мусора и потребление памяти слишком высоки, чтобы получить очень небольшую выгоду.

Вместо этого вы можете использовать Optionals в качестве возвращаемых значений геттеров. Это гарантирует, что вы объясняете, что ваш API может возвращать или не возвращать отсутствующие значения. Кроме того, базовая бизнес-логика не страдает от накладных расходов на производительность при проверке нулевых значений.

Вызывающий вызовет метод получения, интерпретирует результат и перейдет к следующему этапу. JVM хорошо обрабатывает эти короткоживущие объекты [2].

Следует иметь в виду, что Optional не является сериализуемым. Если бы ваш объект домена мог быть сериализуемым, вы не смогли бы этого добиться, если какое-либо из его полей было необязательным [2] [3]. Это еще одна причина не использовать его в качестве атрибутов класса.

Использование дополнительных значений

Это святой Грааль необязательных ценностей. Необязательная монада дает нам несколько очень полезных методов для обработки этих значений. Дело в том, что в API также есть некоторые методы, которых следует избегать.

Допустим, мы создаем службу для аутентификации пользователя в системе. Мы должны проверить, не существует ли пользователь. Если нет, создаем новый и возвращаемся клиенту.

Вы не должны использовать Optional::isPresent и Optional::get вместе, поскольку они не помогут вам удалить if-elses из вашего кода. Кроме того, таким образом вы убегаете от возможностей декларативного программирования, которые дает нам Optional.

Кроме того, использование Optional::get выбрасывает NoSuchElementException, если значение отсутствует. Таким образом, использование Optional::isPresent рядом не лучше, чем проверка на null.

Благодаря удобному интерфейсу Optionals также отлично подходят для работы с потоками. Мы можем использовать map и orElse во время обработки потока.

Обратите внимание: orElse возвращает T для Optional ‹T›, когда значение отсутствует. Вы передаете функцию, которая создает это значение, она будет вызывать ее независимо от того, отсутствует значение или нет.

Вместо этого, если у вас нет объекта для передачи orElse, вы должны использовать orElseGet и передать поставщика, который впоследствии создаст объект.

Таким образом, вы не должны передавать функции orElse, особенно когда эти функции выполняют вызовы ввода-вывода (внешние API, база данных, файловая система). Это может привести к ненужным накладным расходам и неизвестным побочным эффектам [4].

Мы также можем использовать Optional::filter. Если значение, заключенное в необязательный элемент, отсутствует, оно возвращается пустым; в противном случае он применяет предикат к значению.

Это делает ваш код более ясным и понятным.

Цепочка методов

Для вас важно знать, что вы не должны создавать опциональные элементы только ради связывания методов. Помимо того, что это может сделать код менее понятным, он зря добавляет накладные расходы сборщика мусора и потребление памяти.

Нулевые значения неплохи, если их контролируют. То же самое и с нулевыми проверками.

Новые возможности JDK 9

JDK 9 привнес в тип Optional некоторые полезные функции, такие как сопоставление с образцом с ifPresentOrElse и stream!

Необязательно :: ifPresentOrElse

Как упоминалось выше, этот метод приносит что-то вроде сопоставления с образцом для Java Optional. Судя по названию метода, это двойная функция, которая получает функцию, применяемую при наличии значения; и функция, выполняемая, когда она отсутствует.

Он работает аналогично map + orElse. Это зависит от вашего предпочтения использовать тот или иной.

Необязательно :: stream

Этот аккуратный. Optional::stream возвращает поток (ага!) С одним элементом, если значение присутствует; в противном случае он возвращает пустой поток. Это удобно при обработке нескольких значений, которые могут не существовать.

Кроме того, это помогает нам сократить длину связанных методов в потоке, заменив filter и map на flatMap(Optional::stream).

Необязательно :: или

Этот метод работает аналогично Optional::orElseGet, который получает от поставщика для получения возвращаемого значения. Однако, в отличие от orElseGet, or возвращает Optional, описывающий его значение, в противном случае он возвращает Optional, созданный функцией-поставщиком.

Это удобно, когда у вас есть необязательное значение, которое, если оно отсутствует, вы должны получить значение в другом месте, которое также может не существовать.

Заключение

Несмотря на то, что это недостаточно используемая функция JDK, класс Optional дает возможность обрабатывать отсутствующие значения в оптимизированном потоке данных с его плавным API и декларативным программированием. Это не противоядие от всех нулевых проверок и нулевых значений в вашей кодовой базе. Тем не менее, это помогает нам создавать более понятные API и безошибочный код.

Вы также должны знать, что неправильное использование может привести к большим проблемам в вашей кодовой базе. Есть некоторые ситуации, когда вы не можете его использовать, например, внутри классов Serializable.

Имейте в виду, что Optional - не единственное решение относительно монадических контейнеров для отсутствующих значений. Интерфейс Vavr Option действительно хорош. Он имеет аналогичный API и, что может быть интересно, расширяет Serializable [6] [7]. В дополнение к этому, у Vavr есть несколько хороших монад для обработки ошибок.

Вот и все, ребята! Надеюсь, вам понравилось, и вы сможете улучшить использование дополнительных значений.

Ваше здоровье.

использованная литература

[1] Необязательный документ javadoc: h ttps: //docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/Optional.html

[2] Java SE 8 Необязательно, прагматичный подход: https://blog.joda.org/2015/08/java-se-8-optional-pragmatic -approach.html

[3] Необязательно, Стюарт Маркс: https://www.youtube.com/watch?v=fBYhtvY19xA

[4] Руководство по Java 8 (необязательно): https://www.baeldung.com/java-optional

[5] Дополнительная информация о Java 8: https://mkyong.com/java8/java-8-optional-in-depth/

[6] Вариант javadoc (API Vavr 0.9.2): https://www.javadoc.io/doc/io.vavr/vavr/0.9.2/io/vavr/control/Option .html

[7] Использование необязательного Java по сравнению с вариантом Vavr: https://dzone.com/articles/using-java-optional-vs-vavr-option