Статьи

Использование необязательного для указания наличия или отсутствия значения в Java

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

public interface OrderService {
  Order findOrderWithId(long orderId);
}

Что первое, что приходит на ум? Скорее всего, это будет вопрос о том, что произойдет, если ордер с запрошенным идентификатором заказа не существует.

Чтобы найти ответ, вы можете:

  • Проверьте документацию по интерфейсу (Что, если документация отсутствует?), Или
  • Посмотрите в исходный код (Что делать, если исходный код не доступен?)

В любом из случаев наибольшая вероятность того, что интерфейс вернет  нулевой ордер .

Клиентская логика, которую вы будете писать для такого интерфейса, будет выглядеть примерно так:

Order order = orderService.findOrderWithId(orderId);
if (order == null) throw new OrderNotFoundException();
order.getTotalAmount();
... // do further processing

В большинстве случаев жизнь разработчика не легка. Есть крайние сроки, которые вы должны соблюдать, и под давлением доставки вы можете забыть добавить эту нулевую проверку  if (order == null). И тогда вы можете увидеть страшные,  NullPointerException когда:

  • вы запускаете свои юнит-тесты (похлопываете по спине за  написание юнит-тестов ), или
  • ваш код  проверяется QA  (спасибо QA за обнаружение ошибки в коде), или
  • в худшем случае, когда ваш код запущен в производство.

Представление о возможности отсутствия значения с помощью необязательного

Это где  Optional приходит на помощь. Он был частью  библиотек Google Guava  долгое время. Это также недавно было включено в  Java 8 . Давайте попробуем переписать интерфейс, используя версию Java 8  Optional.

public interface OrderService {
  Optional<Order> findOrderWithId(long id);
}

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

Посмотрим, как будет выглядеть клиентский код на таком интерфейсе. Приведенный ниже код специально сделан подробным с целью иллюстрации. Более краткая версия также будет следовать.

Optional<Order> orderOptional = orderService.findOrderWithId(orderId);
if (!orderOptional.isPresent()) throw new OrderNotFoundException();
Order order = orderOptional.get(); 
order.getTotalAmount();
... // do further processing

Обратите внимание , что клиентский код от интерфейса называет  get() на  , Optional чтобы получить объект , который также заставляет писатель кода клиента , чтобы думать и обрабатывать случаи , то значение нет. Вы можете использовать isPresent() для проверки наличия или отсутствия значения.

Более краткая версия приведенного выше кода будет.

Order order = orderService.findOrderWithId(orderId)
    .orElseThrow(OrderNotFoundException::new);
order.getTotalAmount();
... // do further processing

Есть несколько заводских методов, таких как  Optional.of(T value)Optional.ofNullable(T value) и Optional.empty() которые могут использоваться на стороне производителя. Со стороны потребителей метода экземпляра любит get()orElse(T other)orElseGet(Supplier<? extends T> other) и orElseThrow(Supplier<? extends X> exceptionSupplier) может быть использована.

Резюме

Используйте  Optional для представления отсутствия или наличия значения. Используйте его для указания типов возврата при разработке интерфейсов или API. Его использование дает следующие преимущества:

  • Код становится документацией  для любого клиента, использующего код.
  • Код  клиента становится менее подверженным ошибкам  и может избежать  NullPointerExceptions из-за халатности.