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

168

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

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) В любом случае, есть ли лучший способ сделать это?

MarcG
источник
4
Похоже, что вещи не всегда были такими , но я просто не могу найти причину.
Эдвин Далорсо

Ответы:

126

Если вы добавите статический импорт для 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 каждый раз, когда он используется в цепочке. Обе операции могут оказать существенное влияние на поведение вашей программы. Однако, если удобочитаемость важнее производительности , это может быть очень полезным подходом.

nosid
источник
1
Я не нахожу concatколлекционера очень читабельным. Кажется странным иметь статический метод с одним параметром, который называется так, а также использовать collectдля объединения.
Дидье Л
@nosid, возможно, немного ортогональный вопрос к этой теме, но почему вы утверждаете It's a bad idea to import static methods with names? Я искренне заинтересован - я считаю, что это делает код более кратким и читабельным, и многие люди, которых я спрашивал, думали так же. Не забудьте привести несколько примеров, почему это вообще плохо?
квант
1
@Quantum: В чем смысл compare(reverse(getType(42)), of(6 * 9).hashCode())? Обратите внимание, что я не говорил, что статический импорт - плохая идея, но статический импорт для общих имен, таких как ofи concatесть.
nosid
1
@nosid: не будет ли быстрое раскрытие значения каждого элемента управления в современной среде IDE? В любом случае, я думаю, что в лучшем случае это может быть утверждение о личных предпочтениях, поскольку я до сих пор не вижу технической причины, по которой статический импорт для «общих» имен является плохим - если только вы не используете Notepad или VI (M) для программирования, в этом случае у тебя большие проблемы.
квант
Я не скажу, что Scala SDK лучше, но ... упс, я это сказал.
Эйрирлар
165

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

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

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

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

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

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

Преобразуется в Streams.concat (Stream, Stream)

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

Преобразуется в Stream.concat (Stream, Stream)

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

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

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

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

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

    Stream.concat(s1, s2)

    не этот:

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

    ... право?

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

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

    не этот:

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

    ... право?

Эдвин Далорсо
источник
6
+1 Хорошее исследование. И я буду использовать это как мой Stream.concat, принимая varargs:public static <T> Stream<T> concat(Stream<T>... streams) { return Stream.of(streams).reduce(Stream.empty(), Stream::concat);}
MarcG
1
Сегодня я написал свою собственную версию Concat, и сразу после этого я финансирую эту тему. Сигнатура немного отличается, но благодаря этому она более общая;) например, вы можете объединить Stream <Integer> и Stream <Double> в Stream <Number>. @SafeVarargs private static <T> Stream<T> concat(Stream<? extends T>... streams) { return Stream.of(streams).reduce(Stream.empty(),Stream::concat).map(Function.identity());}
Кант
@kant Зачем тебе Function.identity()карта? В конце концов, он возвращает тот же аргумент, который получает. Это не должно иметь никакого эффекта в результирующем потоке. Я что-то упускаю?
Эдвин Далорсо
1
Вы пытались напечатать это в своей IDE? Без .map (identity ()) вы получите ошибку компиляции. Я хочу вернуть Stream <T>, но заявление: return Stream.of(streams).reduce(Stream.empty(),Stream::concat)возвращает Stream <? extends T>. (Someting <T> является подтипом Something <? extends T>, а не другим способом, поэтому его нельзя привести). Дополнительное .map(identity())приведение <? расширяет T> до <T>. Это происходит благодаря сочетанию java 8 «целевых типов» аргументов метода и возвращаемых типов и сигнатуры метода map (). На самом деле это Function. <T> identity ().
Кант
1
@kant Я не вижу особого смысла в этом ? extends T, так как вы можете использовать преобразование захвата . В любом случае, вот мой фрагмент кода гисти. Давайте продолжим обсуждение в гисте.
Эдвин Далорсо
12

Моя библиотека 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);
Тагир Валеев
источник
9

Просто сделать:

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

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

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

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

Герман
источник
1

Если вы не возражаете против использования сторонних библиотек, у cyclops-реагирует расширенный тип потока, который позволит вам сделать это с помощью операторов добавления / добавления.

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

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);

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

Джон МакКлин
источник
1

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

Хотя объединение потоков может оказаться громоздким (таким образом, этот поток), объединение их результатов обработки довольно легко.

Ключом к решению является создание собственного сборщика и обеспечение того, чтобы функция поставщика для нового сборщика каждый раз возвращала одну и ту же коллекцию (а не новую ). Этот код иллюстрирует приведенный ниже код.

package scratchpad;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collector;
import java.util.stream.Stream;

public class CombineStreams {
    public CombineStreams() {
        super();
    }

    public static void main(String[] args) {
        List<String> resultList = new ArrayList<>();
        Collector<String, List<String>, List<String>> collector = Collector.of(
                () -> resultList,
                (list, item) -> {
                    list.add(item);
                },
                (llist, rlist) -> {
                    llist.addAll(rlist);
                    return llist;
                }
        );
        String searchString = "Wil";

        System.out.println("After processing first stream\n"
                + createFirstStream().filter(name -> name.contains(searchString)).collect(collector));
        System.out.println();

        System.out.println("After processing second stream\n"
                + createSecondStream().filter(name -> name.contains(searchString)).collect(collector));
        System.out.println();

        System.out.println("After processing third stream\n"
                + createThirdStream().filter(name -> name.contains(searchString)).collect(collector));
        System.out.println();

    }

    private static Stream<String> createFirstStream() {
        return Arrays.asList(
                "William Shakespeare",
                "Emily Dickinson",
                "H. P. Lovecraft",
                "Arthur Conan Doyle",
                "Leo Tolstoy",
                "Edgar Allan Poe",
                "Robert Ervin Howard",
                "Rabindranath Tagore",
                "Rudyard Kipling",
                "Seneca",
                "John Donne",
                "Sarah Williams",
                "Oscar Wilde",
                "Catullus",
                "Alfred Tennyson",
                "William Blake",
                "Charles Dickens",
                "John Keats",
                "Theodor Herzl"
        ).stream();
    }

    private static Stream<String> createSecondStream() {
        return Arrays.asList(
                "Percy Bysshe Shelley",
                "Ernest Hemingway",
                "Barack Obama",
                "Anton Chekhov",
                "Henry Wadsworth Longfellow",
                "Arthur Schopenhauer",
                "Jacob De Haas",
                "George Gordon Byron",
                "Jack London",
                "Robert Frost",
                "Abraham Lincoln",
                "O. Henry",
                "Ovid",
                "Robert Louis Stevenson",
                "John Masefield",
                "James Joyce",
                "Clark Ashton Smith",
                "Aristotle",
                "William Wordsworth",
                "Jane Austen"
        ).stream();
    }

    private static Stream<String> createThirdStream() {
        return Arrays.asList(
                "Niccolò Machiavelli",
                "Lewis Carroll",
                "Robert Burns",
                "Edgar Rice Burroughs",
                "Plato",
                "John Milton",
                "Ralph Waldo Emerson",
                "Margaret Thatcher",
                "Sylvie d'Avigdor",
                "Marcus Tullius Cicero",
                "Banjo Paterson",
                "Woodrow Wilson",
                "Walt Whitman",
                "Theodore Roosevelt",
                "Agatha Christie",
                "Ambrose Bierce",
                "Nikola Tesla",
                "Franz Kafka"
        ).stream();
    }
}
Legna
источник
0

Как насчет написания собственного метода 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;
}

По крайней мере, это делает ваш первый пример намного более читабельным.

Феликс С
источник
1
Будьте осторожны при построении потоков из повторной конкатенации. Доступ к элементу глубоко сцепленного потока может привести к глубоким цепочкам вызовов или даже к StackOverflowError.
Legna