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

137

Я работал с новым опциональным типом в 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();
}

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

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

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

Йона Апплетри
источник
13
Смотрите эту проблему . Чтобы уточнить: это уже будет в Java 9 - если не в будущем обновлении Java 8.
Obicere
Это! Спасибо, не нашел этого в моих поисках.
Йона Апплетри
2
@Obicere Эта проблема здесь не применима, потому что речь идет о поведении на пустом Optional, а не об альтернативном результате . Факультативный уже имеет orElseGet()для того, что нужно OP, только он не генерирует хороший каскадный синтаксис.
Марко Топольник,
1
stackoverflow.com/questions/23773024/…
Алессандро Джуза
1
Хорошие
Джон Детройт,

Ответы:

86

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

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

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

Nicolai
источник
Ницца. Это дополнение должно быть год назад, и я не заметил. Что касается вопроса в вашем блоге, изменение типа возвращаемого значения нарушит бинарную совместимость, поскольку инструкции по вызову байт-кода ссылаются на полную сигнатуру, включая тип возвращаемого значения, поэтому нет возможности изменить тип возвращаемого значения ifPresent. Но, во всяком случае, я думаю, что имя ifPresentне очень хорошее в любом случае. Для всех других методов , не имея «Else» в названии (как map, filter, flatMap), подразумевается , что они не делают ничего , если значение не присутствует, так зачем ifPresent...
Хольгер
Таким образом, добавление Optional<T> perform(Consumer<T> c)метода, позволяющего создавать цепочки perform(x).orElseDo(y)( orElseDoв качестве альтернативы вашему предложению ifEmpty, чтобы быть последовательным elseв имени всех методов, которые могли бы что-то сделать для отсутствующих значений). Вы можете имитировать это performв Java 9 через, stream().peek(x).findFirst()хотя это злоупотребление API, и до сих пор нет способа выполнить a, Runnableне указав Consumerодновременно ...
Holger
65

Самый чистый подход «попробуй сервисы», учитывая текущий 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.

Holger
источник
13
просто использовал это в проекте, слава богу, мы не делаем обзоры кода, хотя.
Илья Смагин
3
Это намного чище, чем цепочка "orElseGet", но также намного сложнее для чтения.
Слартидан
4
Это, безусловно, правильно ... но, честно говоря, мне нужно секунду, чтобы разобрать его, и я не уверена, что это правильно. Имейте в виду, я понимаю, что этот пример есть, но я мог бы представить небольшое изменение, которое сделало бы его не лениво оцененным или имело бы какую-то другую ошибку, которая была бы неразличимой с первого взгляда. Для меня это относится к категории «будь приличным, как функция полезности».
Йона Апплетри
3
Интересно, будет ли переключение .map(Optional::get)с « .findFirst()легче» «читать», например, .filter(Optional::isPresent).findFirst().map(Optional::get)можно «прочитать» как «найти первый элемент в потоке, для которого Optional :: isPresent имеет значение true, а затем сгладить его, применив Optional :: get»?
Schatten
3
Забавно, я отправил очень похожее решение для подобного вопроса несколько месяцев назад. Это первый раз, когда я сталкиваюсь с этим.
Шмосель
34

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

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)
Миша
источник
1
да, когда я использую эту стратегию, eclipse говорит: «Метод orElseGet (Supplier <? extends String>) в типе Optional <String> не применим для аргументов (() -> {})» рассмотреть вопрос о возвращении Optional действительной стратегии на строку?
Крисмаркс,
@chrismarx () -> {}не возвращает Optional. Что вы пытаетесь достичь?
Миша
1
Я просто пытаюсь следовать примеру. Цепочка вызовов на карте не работает. Мои сервисы возвращают строки, и, конечно, тогда нет опции .map ()
chrismarx
1
Отличная читабельная альтернатива грядущей or(Supplier<Optional<T>>)Java 9
Дэвид М.
1
@ Смотри, ты ошибаешься. .map()на пустом Optionalбудет производить пустое Optional.
Миша,
27

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

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

result = Optional.ofNullable(serviceA().orElseGet(
                                 () -> serviceB().orElseGet(
                                     () -> serviceC().orElse(null))));
aioobe
источник
2
Это то, что обычно делают гении. Оцените необязательное как обнуляемое, и заверните его ofNullableв самое крутое, что я когда-либо видел.
Джин Квон
5

Предполагая, что вы все еще на 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).

Sheepy
источник
Я не нашел Optional<T>.or(Supplier<Optional<T>>)в документах Guava. У вас есть ссылка для этого?
Тамас Гегедус
.orElseGet возвращает T, но Optional :: empty возвращает Необязательный <T>. Optional.ofNullable (........ orElse (null)) имеет желаемый эффект, как описано @aioobe.
Мигель Перейра
@TamasHegedus Вы имеете в виду вариант № 1? Это пользовательская реализация, которая находится над ней. PS: извините за поздний ответ
Sheepy
@MiguelPereira .orElseGet в этом случае возвращает Optional <T>, потому что он работает на Optional <Optional <T >>
Sheepy
2

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

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

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

  optional(serviceA(args)).visit(some -> some , 
                                 () -> optional(serviceB(args)).visit(some -> some,
                                                                      () -> serviceC(args)));
Джон МакКлин
источник