Развертывание нескольких необязательных переменных в Java 8

У меня есть следующая проблема. Скажем, у вас есть 2 Optional variables

Optional<Contact> c1 = ...
Optional<Contact> c2 = ...

и метод, который требует 2 переменных типа Contact

void match(Contact c1, Contact c2) {...}

и вам нужно развернуть оба c1 и c2 Optional vars и передать их в метод match().

Мой вопрос "Какой самый элегантный способ сделать это в Java 8?"

До сих пор я нашел 2 способа:

  • используя isPresent

    if (c1.isPresent() && c2.isPresent()) {
        match(c1.get(), c2.get());
    }
    
  • используя вложенный ifPresent

    c1.ifPresent((Contact _c1) -> {
        c2.ifPresent((Contact _c2) -> {
            match(_c1, _c2);
        });
    });
    

Оба способа ужасны, на мой взгляд. В Scala я могу сделать это:

for {
    contact1 <- c1
    contact2 <- c2
} yield {
    match(contact1, contact2);
}

Есть ли способ в Java 8 сделать это более аккуратным, чем указано выше?

Ответ 1

Решение, которое вы предоставили в Scala, является просто синтаксическим сахаром для внутреннего использования flatMaps. Вы также можете использовать flatmaps в Java 8 (но для него нет синтаксического сахара).

c1.flatMap(contact1 -> c2.flatMap(contact2 -> match(contact1, contact2)));

это почти то же самое, что и решение 2, которое вы предоставили. Вы также можете использовать аппликативный функтор из https://github.com/aol/cyclops-react (я один из авторов) или любую другую функциональную библиотеку Java 8.

Аппликативный функтор

Optional<String> o3 = Maybe.fromOptional(o1).ap2(String::concat).ap(o2).toOptional();

Для понимания,

Do.add(o1)
    .add(o2)
    .yield(a->b->a.concat(b));

Ответ 2

Вы можете отключить понимание Scala для map/flatMap в Java 8 с помощью такой функции:

public static <T,G,V> Optional<V> map2(Optional<T> opt1, Optional<G> opt2, BiFunction<T, G, V> f) {
    Optional<V> result = opt1.flatMap(t1 -> opt2.map(t2 -> f.apply(t1, t2)));
    return result;
}

А затем передать свою функцию соответствия

Ответ 3

Если вы считаете аргументы, не имеющие значений в качестве исключения, вы можете обрабатывать их, например:

try {
    match(c1.orElseThrow(NoVal::new), c2.orElseThrow(NoVal::new));
} catch (NoVal ex) {
    ...
}

Если они не являются исключительным случаем, я бы пошел с вашим первым вариантом как более конкретным в отношении ваших намерений. На мой взгляд, он довольно понятен и читабельен и легко заменяется на использование orElse, если вы хотите переключиться на использование значений по умолчанию, если пустые опции.