Добавляйте префикс и суффикс в Collectors.joining (), только если присутствует несколько элементов - PullRequest
0 голосов
/ 05 октября 2018

У меня есть поток строк:

Stream<String> stream = ...;

Я хочу создать строку, которая объединяет эти элементы с , в качестве разделителя.Я делаю это следующим образом:

stream.collect(Collectors.joining(","));

Теперь я хочу добавить к этому выводу префикс [ и суффикс ], только если было несколько элементов.Например:

  • a
  • [a,b]
  • [a,b,c]

Это можно сделать без предварительной материализацииStream<String> до List<String> и затем проверка List.size() == 1?В коде:

public String format(Stream<String> stream) {
    List<String> list = stream.collect(Collectors.toList());

    if (list.size() == 1) {
        return list.get(0);
    }
    return "[" + list.stream().collect(Collectors.joining(",")) + "]";
}

Странно сначала преобразовать поток в список, а затем снова в поток, чтобы иметь возможность применить Collectors.joining(",").Я думаю, что неоптимально обходить весь поток (который выполняется в течение Collectors.toList()) только для обнаружения наличия одного или нескольких элементов.

Я мог бы реализовать свой собственный Collector<String, String>, которыйподсчитывает количество заданных предметов и использует это количество впоследствии.Но мне интересно, есть ли прямой путь.

Этот вопрос намеренно игнорирует случай, когда поток пуст.

Ответы [ 2 ]

0 голосов
/ 06 октября 2018

Уже принят ответ, и я тоже проголосовал за него.

Тем не менее, я хотел бы предложить потенциально другое решение.Потенциально, потому что у него есть одно требование:
stream.spliterator() из Stream<String> stream должно быть Spliterator.SIZED.

Если это применимо к вашему делу, вы также можете использовать это решение:

  public String format(Stream<String> stream) {
    Spliterator<String> spliterator = stream.spliterator();
    StringJoiner sj = spliterator.getExactSizeIfKnown() == 1 ?
      new StringJoiner("") :
      new StringJoiner(",", "[", "]");
    spliterator.forEachRemaining(sj::add);

    return sj.toString();
  }

Согласно JavaDoc Spliterator.getExactSizeIfKnown() "возвращает estimateSize(), если Spliterator равно SIZED, иначе -1."Если Spliterator равно SIZED, то «estimateSize() до обхода или разбиения представляет конечный размер, который в отсутствие структурной модификации источника представляет точный подсчет числа элементов, которыестолкнуться с полным обходом. "

Поскольку" большинство Spliterator для коллекций, которые охватывают все элементы Collection, сообщают об этой характеристике "(Примечание API в JavaDoc SIZED), это может быть желательным Directer Way .

РЕДАКТИРОВАТЬ:
Если Stream пусто, мы можем вернуть пустое String сразу.Если Stream имеет только один String, нет необходимости создавать StringJoiner и копировать String в него.Мы возвращаем сингл String напрямую.

  public String format(Stream<String> stream) {
    Spliterator<String> spliterator = stream.spliterator();

    if (spliterator.getExactSizeIfKnown() == 0) {
      return "";
    }

    if (spliterator.getExactSizeIfKnown() == 1) {
      AtomicReference<String> result = new AtomicReference<String>();
      spliterator.tryAdvance(result::set);
      return result.get();
    }

    StringJoiner result = new StringJoiner(",", "[", "]");
    spliterator.forEachRemaining(result::add);
    return result.toString();
  }
0 голосов
/ 05 октября 2018

Да, это возможно при использовании пользовательского экземпляра Collector, который будет использовать анонимный объект с количеством элементов в потоке и перегруженный метод toString():

public String format(Stream<String> stream) {
    return stream.collect(
            () -> new Object() {
                StringJoiner stringJoiner = new StringJoiner(",");
                int count;

                @Override
                public String toString() {
                    return count == 1 ? stringJoiner.toString() : "[" + stringJoiner + "]";
                }
            },
            (container, currentString) -> {
                container.stringJoiner.add(currentString);
                container.count++;
            },
            (accumulatingContainer, currentContainer) -> {
                accumulatingContainer.stringJoiner.merge(currentContainer.stringJoiner);
                accumulatingContainer.count += currentContainer.count;
            }
                         ).toString();
}

Объяснение

Collector интерфейс имеет следующие методы:

public interface Collector<T,A,R> {
    Supplier<A> supplier();
    BiConsumer<A,T> accumulator();
    BinaryOperator<A> combiner();
    Function<A,R> finisher();
    Set<Characteristics> characteristics();
}

Я опущу последний метод, так как он не подходит для этого примера.

Существует метод collect() со следующей подписью:

<R> R collect(Supplier<R> supplier,
              BiConsumer<R, ? super T> accumulator,
              BiConsumer<R, R> combiner);

, и в нашем случае он разрешит:

<Object> Object collect(Supplier<Object> supplier,
              BiConsumer<Object, ? super String> accumulator,
              BiConsumer<Object, Object> combiner);
  • В supplier мыиспользуют экземпляр StringJoiner (в основном то же самое, что Collectors.joining() использует).
  • В accumulator мы используем StringJoiner::add(), но мы также увеличиваем счет
  • В combiner мы используем StringJoiner::merge() и добавляем счет в аккумулятор
  • Прежде чем вернуться из функции format(), нам нужно вызвать метод toString(), чтобы обернуть наш накопленный экземпляр StringJoiner в [] (или оставить его как есть, в случае одноэлементного потока

Также можно добавить футляр для пустого футляра, я оставил его, чтобы не усложнять этот коллектор.

...