Java 8 Отображение нескольких необязательных параметров в функцию - PullRequest
5 голосов
/ 05 марта 2019

Допустим, у меня есть функция 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());
}

Есть ли более чистый способ сделать это?

Ответы [ 4 ]

3 голосов
/ 05 марта 2019

Вы только что наткнулись на концепцию под названием поднятие в функциональном программировании, которая позволяет поднимать обычные функции (например, A -> B) в новые домены (например, Optional<A> -> Optional<B>).).

Существует также синтаксический сахар для flatMapping и картирования, более удобно называемый нотацией do в 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 и т. Д.

3 голосов
/ 05 марта 2019

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

if (Stream.of(a, b).allMatch(Optional::isPresent)) {
    return f(a.get(), b.get());
}
1 голос
/ 05 марта 2019

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

Я считаю, что это чище и проще, чем вашвариант 2:

String a = getA().orElse(null);
String b = getB().orElse(null);
if(a != null && b != null) {
    return f(a, b);
}
0 голосов
/ 05 марта 2019

Если вы уверены, что 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");
...