Как присоединиться к двум необязательным <String> с разделителем в Java 8

У меня две строки Optional, name1 и name2. Я хочу присоединиться к двум таким образом, чтобы результат также был необязательным:

  • Если один из них не является пустым, результатом должно быть непустое имя.
  • Если оба значения не являются пустыми, я хочу, чтобы результат был связан с разделителем AND.
  • Если оба пустые, результат должен быть пустым Optional

Моя попытка:

StringBuilder sb = new StringBuilder();
name1.ifPresent(sb::append);
name2.ifPresent(s -> {
    if (sb.length() > 0) {
        sb.append(" AND ");
    }
    sb.append(s);
}
Optional<String> joinedOpt = Optional.ofNullable(Strings.emptyToNull(sb.toString()));

Это работает, но кажется уродливым и не очень функциональным.

PS: Существует аналогичный вопрос, но принятый ответ неверен. В частности, если name1 пуст, а name2 - нет, он возвращает пустой необязательный.

Ответ 1

Одним из решений является поток и reduce():

Optional<String> joinedOpt = Stream.of(name1, name2)
        .filter(Optional::isPresent)
        .map(Optional::get)
        .reduce((a, b) -> a + " AND " + b);

Не стесняйтесь заменить комбинацию фильтра/карты с Java 9 или Guava, как предложили другие.

Ответ 2

Возможное решение в Java 1.9 было бы

 Optional<String> name1 = Optional.of("x");
 Optional<String> name2 = Optional.of("y");
 String s = Stream.concat(name1.stream(), name2.stream()).collect(Collectors.joining(" AND "));
 System.out.println(s);

С Java 1.8 нет способа переходить от опции Необязательно к потоку. Однако вы можете легко добавить такое преобразование в Java 1.8.

static <T> Stream<T> of(Optional<T> option) {
    return option.map(Stream::of).orElseGet(Stream::empty);
}

И теперь вы можете использовать метод of, чтобы объединить оба потока. Теперь, чтобы получить Необязательный, пустой, если нет результата, вы можете обернуть результат в Необязательный и сделать простой фильтр.

Optional<String> result = Optional.of(collect).filter(s -> !s.isEmpty());

Ответ 3

Немного отличающийся аромат с использованием Collectors.joining:

    Optional<String> result  = Optional.of( 
        Stream.of(so1, so2)
        .filter(Optional::isPresent)
        .map(Optional::get)
        .collect( Collectors.joining(" AND ") ) 
    ).filter( s -> !s.isEmpty() ); 

Что мне нравится в Collectors.joining, так это то, что потоки Java могут потенциально повторно использовать один класс StringBuilder вместо создания нового при выполнении операции +.

Ответ 4

Иногда попытка работать с Optional (или Stream) просто затушевывает то, что вы пытаетесь выразить в своем коде.

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

Optional<String> joinedOpt;
if (name1.isPresent() && name2.isPresent()) {
    joinedOpt = Optional.of(name1.get() + " AND " + name2.get());
} else {
    joinedOpt = name1.isPresent() ? name1 : name2;
}

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

Ответ 5

Близко к принятому ответу

Optional<String> joinedOpt = Stream.of(name1, name2)
            .flatMap(x -> x.map(Stream::of).orElse(null))
            .reduce((a, b) -> a + " AND " + b);

Или в java-9:

Optional<String> joinedOpt = Stream.of(name1, name2)
            .flatMap(Optional::stream)
            .reduce((a, b) -> a + " AND " + b);

Ответ 6

Вот потоковое решение, которое фильтрует те Optional, которые присутствуют, затем извлекает элементы и использует встроенный Collector для объединения остальных элементов. Затем он проверяет, является ли результат пустым в конце, чтобы вернуть пустой Optional.

Он принимает List of Optional, поэтому он охватывает более общий случай, чем только два элемента.

public Optional<String> optionalJoin(List<Optional<String>> strings) {
    String result = strings.stream()
        .filter(Optional::isPresent)
        .map(Optional::get)
        .collect(Collectors.joining(" AND "));
    return result.isEmpty() ? Optional.empty() : Optional.of(result);
}

Ответ 7

Однострочный без потоков, но не особенно элегантный:

    public static Optional<String> concat(Optional<String> name1, Optional<String> name2) {
        return Optional.ofNullable(name1.map(s1 -> name2.map(s2 -> s1 + " AND " + s2).orElse(s1)).orElse(name2.orElse(null)));
    }

    public static void main(String[] args) {
        System.out.println(concat(Optional.of("A"), Optional.of("B")));
        System.out.println(concat(Optional.of("A"), Optional.empty()));
        System.out.println(concat(Optional.empty(), Optional.of("B")));
        System.out.println(concat(Optional.empty(), Optional.empty()));
    }