Добавление двух потоков Java 8 или дополнительного элемента в поток - программирование

Добавление двух потоков Java 8 или дополнительного элемента в поток

Я могу добавить потоки или дополнительные элементы, например:

Stream stream = Stream.concat(stream1, Stream.concat(stream2, Stream.of(element));

И я могу добавить новые вещи, как я иду, например:

Stream stream = Stream.concat(
                       Stream.concat(
                              stream1.filter(x -> x!=0), stream2)
                              .filter(x -> x!=1),
                                  Stream.of(element))
                                  .filter(x -> x!=2);

Но это уродливо, потому что concat является статическим. Если concat был методом экземпляра, приведенные выше примеры были бы намного легче читать:

 Stream stream = stream1.concat(stream2).concat(element);

и

 Stream stream = stream1
                 .filter(x -> x!=0)
                 .concat(stream2)
                 .filter(x -> x!=1)
                 .concat(element)
                 .filter(x -> x!=2);

Мой вопрос:

1) Есть ли веская причина, почему concat является статическим? Или есть какой-то эквивалентный метод экземпляра, который мне не хватает?

2) В любом случае, есть ли лучший способ сделать это?

4b9b3361

Ответ 1

Если вы добавите статический импорт для Stream.concat и Stream.of, первый пример можно записать следующим образом:

Stream<Foo> stream = concat(stream1, concat(stream2, of(element)));

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

public static <T> Stream<T> concat(Stream<? extends T> lhs, Stream<? extends T> rhs) {
    return Stream.concat(lhs, rhs);
}
public static <T> Stream<T> concat(Stream<? extends T> lhs, T rhs) {
    return Stream.concat(lhs, Stream.of(rhs));
}

С помощью этих двух статических методов (необязательно в сочетании со статическим импортом) два примера могут быть записаны следующим образом:

Stream<Foo> stream = concat(stream1, concat(stream2, element));

Stream<Foo> stream = concat(
                         concat(stream1.filter(x -> x!=0), stream2).filter(x -> x!=1),
                         element)
                     .filter(x -> x!=2);

Теперь код значительно короче. Однако я согласен с тем, что читаемость не улучшилась. Поэтому у меня есть другое решение.


Во многих ситуациях коллекторы могут использоваться для расширения функциональности потоков. С двумя сборщиками внизу два примера можно записать следующим образом:

Stream<Foo> stream = stream1.collect(concat(stream2)).collect(concat(element));

Stream<Foo> stream = stream1
                     .filter(x -> x!=0)
                     .collect(concat(stream2))
                     .filter(x -> x!=1)
                     .collect(concat(element))
                     .filter(x -> x!=2);

Единственное различие между вашим желаемым синтаксисом и синтаксисом выше состоит в том, что вам нужно заменить concat (...) на collect (concat (...)). Два статических метода могут быть реализованы следующим образом (необязательно, в сочетании со статическим импортом):

private static <T,A,R,S> Collector<T,?,S> combine(Collector<T,A,R> collector, Function<? super R, ? extends S> function) {
    return Collector.of(
        collector.supplier(),
        collector.accumulator(),
        collector.combiner(),
        collector.finisher().andThen(function));
}
public static <T> Collector<T,?,Stream<T>> concat(Stream<? extends T> other) {
    return combine(Collectors.toList(),
        list -> Stream.concat(list.stream(), other));
}
public static <T> Collector<T,?,Stream<T>> concat(T element) {
    return concat(Stream.of(element));
}

Конечно, есть недостаток в этом решении, о котором следует упомянуть. collect - это заключительная операция, которая потребляет все элементы потока. Кроме того, коллекционер concat создает промежуточный ArrayList каждый раз, когда он используется в цепочке. Обе операции могут существенно повлиять на поведение вашей программы. Однако, если читаемость важнее производительности, это может быть очень полезным подходом.

Ответ 2

К сожалению, этот ответ, вероятно, мало или вообще не помогает, но я сделал криминалистический анализ списка Java Lambda Mailing, чтобы узнать, могу ли я найти причину этого дизайна. Это то, что я узнал.

В начале был метод экземпляра для Stream.concat(Stream)

В списке рассылки я четко вижу, что метод был первоначально реализован как метод экземпляра, так как вы можете прочитать в этой теме Пол Сандос, о конкатентной операции.

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

Вы видите в этом другом потоке, что некоторые ранние пользователи JDK 8 задавали вопрос о поведении метода concat-экземпляра при использовании с нулевыми аргументами.

Этот другой поток показывает, что дизайн метода concat обсуждался.

Рефакторинг для Streams.concat(поток, поток)

Но без каких-либо объяснений, внезапно, методы были изменены на статические методы, как вы можете видеть в этой теме о объединении потоков. Это, пожалуй, единственный почтовый поток, который проливает немного света на это изменение, но мне было недостаточно ясно определить причину рефакторинга. Но мы можем видеть, что они сделали фиксацию, в которой они предложили переместить метод concat из Stream и в класс-помощник Streams.

Рефакторинг для Stream.concat(поток, поток)

Позже он был перемещен снова от Streams до Stream, но опять же, никаких объяснений для этого.

Итак, в нижней строке, причина для дизайна не совсем понятна для меня, и я не мог найти хорошее объяснение. Думаю, вы все равно можете задать вопрос в списке рассылки.

Некоторые альтернативы для конкатенации потоков

Этот другой поток Майкла Хиксона обсуждает/спрашивает о других способах объединения /concat потоков

  • Чтобы объединить два потока, я должен сделать это:

    Stream.concat(s1, s2)
    

    не это:

    Stream.of(s1, s2).flatMap(x -> x)
    

    ... правильно?

  • Чтобы объединить более двух потоков, я должен сделать это:

    Stream.of(s1, s2, s3, ...).flatMap(x -> x)
    

    не это:

    Stream.of(s1, s2, s3, ...).reduce(Stream.empty(), Stream::concat)
    

    ... правильно?

Ответ 3

Моя библиотека StreamEx расширяет функциональность Stream API. В частности, он предлагает такие методы, как append и prepend, которые решают эту проблему (внутренне они используют concat). Эти методы могут принимать либо другой массив потока или коллекций, либо массив varargs. Используя мою библиотеку, ваша проблема может быть решена таким образом (обратите внимание, что x != 0 выглядит странно для не-примитивного потока):

Stream<Integer> stream = StreamEx.of(stream1)
             .filter(x -> !x.equals(0))
             .append(stream2)
             .filter(x -> !x.equals(1))
             .append(element)
             .filter(x -> !x.equals(2));

Кстати, есть также ярлык для вашей операции filter:

Stream<Integer> stream = StreamEx.of(stream1).without(0)
                                 .append(stream2).without(1)
                                 .append(element).without(2);

Ответ 4

Просто выполните:

Stream.of(stream1, stream2, Stream.of(element)).flatMap(identity());

где identity() - статический импорт Function.identity().

Объединение нескольких потоков в один поток аналогично сглаживанию потока.

Однако, к сожалению, по какой-либо причине на Stream нет метода flatten(), поэтому вам нужно использовать flatMap() с функцией идентификации.

Ответ 5

Если вы не возражаете против использования сторонних библиотек cyclops-react имеет расширенный тип Stream, который позволит вам сделать это через операторы append/prepend.

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

Stream stream = ReactiveSeq.of(1,2)
                           .filter(x -> x!=0)
                           .append(ReactiveSeq.of(3,4))
                           .filter(x -> x!=1)
                           .append(5)
                           .filter(x -> x!=2);

[Раскрытие информации Я ведущий разработчик реакции циклопов]

Ответ 6

Вы можете использовать Guava Streams. concat (Stream... streams), который будет очень коротким со статическим импортом:

Stream stream = concat(stream1, stream2, of(element));

Ответ 7

Как написать собственный метод concat?

public static Stream<T> concat(Stream<? extends T> a, 
                               Stream<? extends T> b, 
                               Stream<? extends T> args)
{
    Stream<T> concatenated = Stream.concat(a, b);
    for (Stream<T> stream : args)
    {
        concatenated = Stream.concat(concatenated, stream);
    }
    return concatenated;
}

Это, по крайней мере, делает ваш первый пример более удобочитаемым.