Довольно давно, примерно в 2014 году, была выпущена Java 8, которая стала новым стандартом для экосистемы Java. Одна из функций, включенных в этот выпуск, была необязательной. Простой, но очень полезный класс для обработки объектов и значений, допускающих значение NULL.

Быстрый переход к 2023 году и опционально, состоит из прибл. 500 строк кода и 20 методов с общедоступным уровнем доступа (включая hashCode, equals, toString и Javadocs) с различными возможностями и широко используются во всей экосистеме Java. Но как он менялся со временем, что добавлялось и что удалялось? Ответ на этот вопрос — главная тема сегодняшней статьи.

Почему в Java существует необязательное?

Многие современные языки программирования имеют функцию (или ошибку, в зависимости от того, что вы думаете), называемую ошибкой на миллиард долларов или нулевым указателем, если хотите — в основном указателем, содержащим ссылку на недопустимый объект. Такой подход делает возможным появление таких исключений, как NullPointerException.

NullPointerException — одно из наиболее распространенных, если не самое распространенное исключение во время выполнения. Он имеет тенденцию появляться в самых неожиданных и нежелательных местах нашей кодовой базы, доставляя массу неприятностей нам и нашим клиентам.

Конечно, разные языки использовали разные подходы к решению этой проблемы. Так что есть специальные операторы, которые разрешают доступ к свойству с нулевой безопасностью, например ? в машинописном тексте. Другие, такие как Kotlin, не допускают нулевых указателей на уровне языка.

В Java до выпуска Java 8 единственным способом справиться с ними было использование «лестницы if», отвечающей за проверку того, не является ли объект нулевым, что могло сделать наш код похожим на следующий пример.

private String getAssignManagerName(Task task) {
  if (task != null) {
      Team team = task.getAssignTeam();
      if (team != null) {
          Manager manager = team.getManager();
          if (manager != null) {
              return manager.getName();
          }
      }
  }
  throw new UnableToGetManagerNameException("Unable to get assign manager name");
}

Как видите, этот код довольно сложный и длинный. Мы должны сделать каждую ненулевую проверку явно и в отдельном выражении, поэтому мы получили много шаблонов. Эта проблема была одной из основных причин введения опциона в Java. Ниже вы можете увидеть тот же оператор, но измененный с использованием опционального.

private String getAssignUserFirstName(Task task) {
    return Optional.ofNullable(task)
            .map(Task::getAssignTeam)
            .map(Team::getManager)
            .map(Manager::getName)
            .orElseThrow(() -> new UnableToGetManagerNameException("Unable to get assign manager name"));
}

Как видите, почти все делается за нас. Во-первых, мы обертываем нашу задачу опциональным. Затем мы выполняем над ним последовательность операций map. Наконец, если наш Optional пуст или в какой-то момент мы не смогли получить значение, мы выбрасываем исключение с помощью orElseThrow. Здесь нет стандартного кода, и пример менее обширен, чем основанный на if.

Я оставляю вам решать, какой из них более удобочитаем и значим для вас.

Эволюция необязательных версий Java

Ява 8

Необязательный был введен в экосистему Java, помимо таких методов, как map, orElseThrow и ofNullable, он содержит другие полезные методы:

  • of(T value), который похож на ofNullable(T value), но его следует использовать только тогда, когда вы хотите создать необязательные значения из ненулевых значений.
  • flatMap(Function<? super T,Optional<U>> mapper) Если присутствует значение внутри значения «Необязательный», выполняются операции, возвращающие «Необязательный» и обрабатывающие сведение вложенных «Дополнительных» результатов к одному «Необязательный». Если необязательный параметр пуст, ничего не происходит.
  • filter(Predicate<? super T> predicate) Если значение внутри Optional присутствует, проверьте, соответствует ли оно определенному предикату. Если необязательный параметр пуст, ничего не происходит.
  • ifPresent(Consumer<? super T> consumer)Принимает потребительский интерфейс и вызывает его, когда присутствует значение в необязательном, в противном случае он ничего не делает. Очень полезно для любого типа действий с побочными эффектами, так как возвращает пустоту.

Я намеренно не стал описывать все методы из Java 8 Optional, так как в этой статье основное внимание должно быть уделено изменениям в API, а не тому, как он изначально выглядел. Если вы хотите более подробно ознакомиться со всеми методами, перейдите по ссылке на официальную документацию Oracle.

Ява 9

В этой версии в наш API добавлено наибольшее количество новых методов, а именно:

  • ifPresentOrElse(Consumer<? super T> action, Runnable emptyAction)Если значение присутствует, оно применит к нему действие Consumer, в противном случае оно выполнит предоставленное исполняемое действие. По смыслу аналогичен оригинальному ifPresent, но также позволяет обрабатывать пустые опции.
  • or(Supplier<? extends Optional<? extends T>> supplier)Если значение отсутствует, этот метод вернет необязательный параметр, предоставленный функцией поставщика. Мы можем дальше работать с этим значением на последующих шагах.
  • stream()Если наше значение присутствует, оно преобразует наш необязательный параметр в поток, содержащий это значение. Если нет, мы получаем пустой поток. Такое преобразование дает нам доступ ко всем методам, объявленным в Stream API, который является более мощным и предоставляет больше функций, чем необязательный API.

Ява 10

В этой версии Oracle решил добавить только один новый метод в класс Optional. С одной стороны вроде бы и полезно, а с другой — не очень. Я опишу его и предоставлю вам оценить, насколько он может быть полезен.

  • orElseThrow()Имеют то же значение, что и исходный orElseThrow, но исключение, которое будет выдано, определено для нас, поэтому нам не нужно его указывать. Он всегда будет бросатьNoSuchElementException.

Ява 11

В Java 11 мы снова получаем только один новый метод. Что, на мой взгляд, не очень полезно.

  • isEmpty()Это работает противоположно isPresent() из Java 8 — возвращает True, если значение отсутствует в нашем необязательном экземпляре.

Java 12 и выше

К сожалению, после Java 12 обновлений в Optional API не было. Если такие изменения появятся, эта статья будет обновлена.

Подведение итогов

Благодаря трем выпускам Java, Необязательный получил 6 новых методов, что составляет прибл. 25% всех методов в классе Optional. С ними мы получили новые функции и возможные варианты использования. Я надеюсь, что эта статья дала вам лучшее представление о том, как менялся необязательный API с последующими выпусками Java и почему было бы лучше использовать Java 9 или 11 вместо Java 8. Спасибо за ваше время.

Первоначально опубликовано на https://dzone.com.