Java 8 Отображение нескольких необязательных параметров в функцию

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

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

однозначно предпочитаю второй путь, первый непонятен и функциональная каша

Lino 05.03.2019 17:47

Тот факт, что вы используете get() в первом случае, означает, что вы получите исключение, если любой из Optional пуст, так что это определенно не первый метод.

RealSkeptic 05.03.2019 17:52

Любопытно узнать, что является еще частью (2), которая могла бы быть orElse.. кандидатом на (1). Это неясная проблема, и она вызывает мнения ... и еще один вопрос, который, по крайней мере, озадачивает меня, заключается в том, что делает метод f, чтобы вам нужно было убедиться, что оба (/ все) значения присутствуют перед его вызовом? Просто интересно, может ли f принять варарги, такие как Object f(String... args)?

Naman 05.03.2019 18:43
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
6
3
1 733
4
Перейти к ответу Данный вопрос помечен как решенный

Ответы 4

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

if (Stream.of(a, b).allMatch(Optional::isPresent)) {
    return f(a.get(), b.get());
}

Вам все еще нужны первые две строки # 2, так как именно это лучше? Это просто дольше.

Michael 05.03.2019 18:11
Ответ принят как подходящий

Вы только что наткнулись на концепцию под названием подъем в функциональном программировании, которая позволяет вам использовать обычные функции поднимать (например, 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);
}

Другие вопросы по теме