it-swarm.com.ru

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

151
MarcG

Если вы добавите статический импорт for 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);

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


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

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

120
nosid

К сожалению, этот ответ, вероятно, мало или вообще не поможет, но я провел судебный анализ списка рассылки 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)
    

    ... право?

155
Edwin Dalorzo

Моя 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);
12
Tagir Valeev

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

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

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

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

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

9
herman

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

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

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

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();
    }
}
1
Legna

Если вы не возражаете против использования сторонних библиотек 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
John McClean

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

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

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

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

0
Felix S