Допустим, у меня есть функция Object f(String a, String b), и я хочу вызвать две разные функции, которые возвращают необязательные строки, чтобы получить параметры для f Optional<String> getA() и Optional<String> getB(). Я могу придумать два решения, но ни одно из них не выглядит таким уж чистым, особенно когда у вас еще больше параметров:
1:
return getA().flatMap(
a -> getB().map(
b -> f(a,b)).get()
2:
Optional<String> a = getA();
Optional<String> b = getB();
if (a.isPresent() && b.isPresent()) {
return f(a.get(), b.get());
}
Есть ли более чистый способ сделать это?
Тот факт, что вы используете get() в первом случае, означает, что вы получите исключение, если любой из Optional пуст, так что это определенно не первый метод.
Любопытно узнать, что является еще частью (2), которая могла бы быть orElse.. кандидатом на (1). Это неясная проблема, и она вызывает мнения ... и еще один вопрос, который, по крайней мере, озадачивает меня, заключается в том, что делает метод f, чтобы вам нужно было убедиться, что оба (/ все) значения присутствуют перед его вызовом? Просто интересно, может ли f принять варарги, такие как Object f(String... args)?




Вы можете передать аргументы и применить условие только один раз, но будет ли это более элегантно, чем ваши решения, в глазах смотрящего:
if (Stream.of(a, b).allMatch(Optional::isPresent)) {
return f(a.get(), b.get());
}
Вам все еще нужны первые две строки # 2, так как именно это лучше? Это просто дольше.
Вы только что наткнулись на концепцию под названием подъем в функциональном программировании, которая позволяет вам использовать обычные функции поднимать (например, A -> B) в новых доменах (например, Optional<A> -> Optional<B>).
Существует также синтаксический сахар для flatMapping и более удобного отображения, называемый сделать запись в Haskell и подобных языках, и для понимания в Scala. Это дает вам возможность сохранить линейность потока и избежать вложенности (что вы были вынуждены пройти в своем примере 1).
Java, к сожалению, не имеет ничего подобного, так как его возможности функционального программирования скудны, и даже Optional не является первоклассным гражданином (на самом деле его не использует ни один стандартный API).
Таким образом, вы застряли с подходами, которые вы уже обнаружили.
Если вам интересны концепции, упомянутые выше, читайте дальше.
Подъем
Предполагая, что у вас есть:
public String f(A a, B b) {
return b + "-" + a;
}
С его эквивалентом Scala:
def f(a: A, b: B) = b + "-" + a
Поднятие f в Option (так же, как Optional в Java) будет выглядеть так (с использованием библиотеки Scalaz, см. здесь для кошек)
val lifted = Monad[Option].lift2(f)
lifted теперь является функцией, эквивалентной:
public Optional<String> f(Optional<A> a, Optional<B> b) {
if (a.isPresent() && b.isPresent()) {
return Optional.of(b + "-" + a);
}
return Optional.empty;
}
Именно то, что вы ищете, в 1 строке и работает для любого контекста (например, List, а не только Option) и любой функции.
Для понимания/обозначения Do
Используя для понимания, ваш пример будет выглядеть так (у меня думать, мой Scala слаб):
for {
a <- getA();
b <- getB();
} yield f(a, b)
И опять же, это применимо ко всему, на что можно нанести плоское отображение, например List, Future и т. д.
Если вы уверены, что оба присутствуют a и b (как, кажется, предполагает ваш последний вызов get в решении 1), я думаю, что это довольно просто:
return f(getA().orElseThrow(() -> new NoSuchElementException("a not present")),
getB().orElseThrow(() -> new NoSuchElementException("b not present")));
Если вы не уверены, что присутствуют оба, я бы предпочел ваше решение 1. Оно использует Optional лучшее. Только я бы не называл get в конце, а скорее orElse или то, что имеет смысл в вашей ситуации, например:
return getA()
.flatMap(a -> getB().map(b -> f(a,b)))
.orElse("Not both present");
Я придерживаюсь мнения, что если нет хорошего способа использовать Optional, то и не стоит пытаться его использовать.
Я считаю, что это чище и проще, чем ваш вариант 2:
String a = getA().orElse(null);
String b = getB().orElse(null);
if (a != null && b != null) {
return f(a, b);
}
однозначно предпочитаю второй путь, первый непонятен и функциональная каша