Необязательный orElse Необязательный в Java

Я работал с новым необязательным типом в Java 8, и я столкнулся с тем, что похоже на общую операцию, которая не является 't поддерживается функционально: "orElseOptional"

Рассмотрим следующий шаблон:

Optional<Result> resultFromServiceA = serviceA(args);
if (resultFromServiceA.isPresent) return result;
else {
    Optional<Result> resultFromServiceB = serviceB(args);
    if (resultFromServiceB.isPresent) return resultFromServiceB;
    else return serviceC(args);
}

Существует много форм этого шаблона, но он сводится к желанию "orElse" на необязательном, который принимает функцию, производящую новую опцию, называемую только в том случае, если текущий не существует.

Реализация будет выглядеть так:

public Optional<T> orElse(Supplier<Optional<? extends T>> otherSupplier) {
    return value != null ? this : other.get();
}

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

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

Кроме того, если кто-нибудь знает, будет ли такой метод включен в JDK 9, и где я могу предложить такой метод? Это похоже на довольно вопиющее упущение API для меня.

Ответ 1

Это часть JDK 9 в форме or, которая берет Supplier<Optional<T>>. Тогда ваш пример:

return serviceA(args)
    .or(() -> serviceB(args))
    .or(() -> serviceC(args));

Подробнее см. Javadoc или этот пост, который я написал.

Ответ 2

Самый чистый подход "попробуйте услуги" с учетом текущего API:

Optional<Result> o = Stream.<Supplier<Optional<Result>>>of(
    ()->serviceA(args), 
    ()->serviceB(args), 
    ()->serviceC(args), 
    ()->serviceD(args))
.map(Supplier::get)
.filter(Optional::isPresent)
.map(Optional::get)
.findFirst();

Важным аспектом является не постоянная цепочка операций, которую вы должны написать один раз, но как легко добавить другую услугу (или изменить список служб в целом). Здесь достаточно добавить или удалить один ()->serviceX(args).

Из-за ленивой оценки потоков никакой сервис не будет вызываться, если предыдущая служба вернула непустую Optional.

Ответ 3

Это не очень, но это сработает:

return serviceA(args)
  .map(Optional::of).orElseGet(() -> serviceB(args))
  .map(Optional::of).orElseGet(() -> serviceC(args))
  .map(Optional::of).orElseGet(() -> serviceD(args));

.map(func).orElseGet(sup) - довольно удобный шаблон для использования с Optional. Это означает: "Если этот Optional содержит значение v, дайте мне func(v), иначе дайте мне sup.get()".

В этом случае мы называем serviceA(args) и получаем a Optional<Result>. Если Optional содержит значение v, мы хотим получить Optional.of(v), но если он пуст, мы хотим получить serviceB(args). Прополощите повтор с другими вариантами.

Другим использованием этого шаблона являются

  • .map(Stream::of).orElseGet(Stream::empty)
  • .map(Collections::singleton).orElseGet(Collections::emptySet)

Ответ 5

Это похоже на хорошую совместимость для сопоставления шаблонов и более традиционный интерфейс Option с реализацией Some и None (например, в Javaslang, FunctionalJava) или ленивый Возможно реализация в cyclops-react. Я автор этой библиотеки.

С cyclops-react вы также можете использовать структурное соответствие шаблону по типам JDK. Для Необязательного вы можете сопоставлять существующие и отсутствующие случаи с помощью шаблона . он будет выглядеть примерно так:

  import static com.aol.cyclops.Matchables.optional;

  optional(serviceA(args)).visit(some -> some , 
                                 () -> optional(serviceB(args)).visit(some -> some,
                                                                      () -> serviceC(args)));

Ответ 6

Предполагая, что вы все еще на JDK8, есть несколько вариантов.

Вариант №1: сделайте свой собственный вспомогательный метод

Например:

public class Optionals {
    static <T> Optional<T> or(Supplier<Optional<T>>... optionals) {
        return Arrays.stream(optionals)
                .map(Supplier::get)
                .filter(Optional::isPresent)
                .findFirst()
                .orElseGet(Optional::empty);
    }
}

Чтобы вы могли это сделать:

return Optionals.or(
   ()-> serviceA(args),
   ()-> serviceB(args),
   ()-> serviceC(args),
   ()-> serviceD(args)
);

Вариант №2: используйте библиотеку

Например, google guava Optional поддерживает правильную or() операцию (как JDK9), например:

return serviceA(args)
  .or(() -> serviceB(args))
  .or(() -> serviceC(args))
  .or(() -> serviceD(args));

(Где каждая из служб возвращает com.google.common.base.Optional, а не java.util.Optional).