Цепочные опции в Java 8
Ищете способ привязки опций, чтобы возвращался первый, который присутствует. Если ни один не присутствует, Optional.empty()
должен быть возвращен.
Предполагая, что у меня есть несколько методов:
Optional<String> find1()
Я пытаюсь связать их:
Optional<String> result = find1().orElse( this::find2 ).orElse( this::find3 );
но, конечно, это не работает, потому что orElse
ожидает, что значение и orElseGet
ожидает a Supplier
.
Ответы
Ответ 1
Используйте поток:
Stream.of(find1(), find2(), find3())
.filter(Optional::isPresent)
.map(Optional::get)
.findFirst();
Если вам нужно оценить методы поиска лениво, используйте функции поставщика:
Stream.of(this::find1, this::find2, this::find3)
.map(Supplier::get)
.filter(Optional::isPresent)
.map(Optional::get)
.findFirst();
Ответ 2
Вы можете сделать это следующим образом:
Optional<String> resultOpt = Optional.of(find1()
.orElseGet(() -> find2()
.orElseGet(() -> find3()
.orElseThrow(() -> new WhatEverException()))));
Хотя я не уверен, что он улучшает читаемость ИМО. Guava обеспечивает способ подключения опций:
import com.google.common.base.Optional;
Optional<String> resultOpt = s.find1().or(s.find2()).or(s.find3());
Это может быть другой альтернативой вашей проблеме, но не использует стандартный дополнительный класс в JDK.
Если вы хотите сохранить стандартный API, вы можете написать простой служебный метод:
static <T> Optional<T> or(Optional<T> first, Optional<T> second) {
return first.isPresent() ? first : second;
}
а затем:
Optional<String> resultOpt = or(s.find1(), or(s.find2(), s.find3()));
Если у вас есть много вариантов для цепей, возможно, лучше использовать подход Stream, как уже упоминалось ранее.
Ответ 3
Вдохновленный ответом Саули, можно использовать метод flatMap()
.
Stream.of(this::find1, this::find2, this::find3)
.map(Supplier::get)
.flatMap(o -> o.map(Stream::of).orElseGet(Stream::empty))
.findFirst();
Преобразование необязательного в поток является громоздким. По-видимому, это будет исправлено с JDK9. Таким образом, это можно записать как
Stream.of(this::find1, this::find2, this::find3)
.map(Supplier::get)
.flatMap(Optional::stream)
.findFirst();
Обновление после выпуска Java 9
Хотя исходный вопрос касался Java 8, Optional::or
был введен в Java 9. С его помощью проблема может быть решена как следует
Optional<String> result = find1()
.or(this::find2)
.or(this::find3);
Ответ 4
Возможно, один из
public <T> Optional<? extends T> firstOf(Optional<? extends T> first, @SuppressWarnings("unchecked") Supplier<Optional<? extends T>>... supp) {
if (first.isPresent()) return first;
for (Supplier<Optional <? extends T>> sup : supp) {
Optional<? extends T> opt = sup.get();
if (opt.isPresent()) {
return opt;
}
}
return Optional.empty();
}
public <T> Optional<? extends T> firstOf(Optional<? extends T> first, Stream<Supplier<Optional<? extends T>>> supp) {
if (first.isPresent()) return first;
Stream<Optional<? extends T>> present = supp.map(Supplier::get).filter(Optional::isPresent);
return present.findFirst().orElseGet(Optional::empty);
}
сделает.
Первый выполняет итерацию по множеству поставщиков. Возвращается первая непустая Optional<>
. Если мы его не найдем, мы возвращаем пустой Optional
.
Второй делает то же самое с Stream
of Suppliers
, который проходит, каждый просил (лениво) их значение, которое затем фильтруется для пустых Optional
s. Возвращается первая непустая, или, если она не существует, пустая.