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


Я работаю с новым необязательный тип в Java 8, и я столкнулся с тем, что кажется обычной операцией, которая не поддерживается функционально: "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.

6 115

6 ответов:

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

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

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

самый чистый подход "try services" с учетом текущего 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.

это не красиво, но это будет работать:

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) и Optional<Result>. Если что Optional содержит значение v, мы хотим получить Optional.of(v), а если он пуст, мы хотим получить serviceB(args). Промыть-повторите с большим количеством альтернатив.

другие виды использования этого шаблон

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

возможно, это то, что вам нужно:получить значение из одного необязательного или другого

в противном случае, вы можете взглянуть на Optional.orElseGet. Вот пример того, что я думаю что вы после:

result = Optional.ofNullable(serviceA().orElseGet(
                                 () -> serviceB().orElseGet(
                                     () -> serviceC().orElse(null))));

предполагая, что вы все еще на 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 опционально поддерживает правильный or() операция (так же, как JDK9), например:

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

(где каждый из сервисов возвращает com.google.common.base.Optional, а не java.util.Optional).

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

С Циклоп-реагировать вы также можете использовать конструкции шаблоны на типах JDK. Для опционного вы можете соответствовать на настоящем моменте и отсутствующие случаи через шаблон Visitor. это будет выглядеть примерно так -

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

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