Додавання двох потоків 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) У будь-якому випадку, чи є кращий спосіб зробити це?


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 (...) на collection (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));
}

Звичайно, є недолік у цьому рішенні, який слід зазначити. збирати - це заключна операція, яка споживає всі елементи потоку. Крім того, колектор CONCAT створює проміжний ArrayList кожен раз , коли він використовується в ланцюзі. Обидві операції можуть мати істотний вплив на поведінку вашої програми. Однак, якщо читабельність важливіша за ефективність , це все ще може бути дуже корисним підходом.


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є.
носід

1
@nosid: Чи не наведення курсору на кожну позицію в сучасному IDE швидко розкриє сенс? У будь-якому випадку, я думаю, що це, мабуть, може бути особистою перевагою в найкращому випадку, оскільки я все ще не бачу жодної технічної причини, чому статичний імпорт для "загальних" імен поганий - якщо ви не використовуєте Блокнот або VI (M) для програмування, в цьому випадку у вас є більші проблеми.
квантовий

Я не збираюся говорити, що Scala SDK краща, але ... ой, я це сказав.
eirirlar

165

На жаль, ця відповідь, мабуть, мало допомагає, але я зробив криміналістичний аналіз списку розсилки Java Lambda, щоб побачити, чи зможу я знайти причину такої розробки. Це я дізнався.

На початку був метод примірника для Stream.concat (Stream)

У списку розсилки я чітко бачу, що метод спочатку був реалізований як метод екземпляра, як ви можете прочитати в цій темі Пола Сандоса про операцію concat.

У ньому вони обговорюють питання, які можуть виникнути з тих випадків, коли потік може бути нескінченним і що з’єднання означало б у тих випадках, але я не думаю, що це було причиною модифікації.

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

Цей інший потік виявляє, однак, що розробка дискусійного методу concat обговорювалася.

Відновлено до Streams.concat (Потік, Потік)

Але без будь-якого пояснення, раптом методи були змінені на статичні методи, як ви бачите в цій темі про поєднання потоків . Це, мабуть, єдиний поштовий потік, який проливає трохи світла на цю зміну, але мені було недостатньо зрозуміло, щоб визначити причину рефакторингу. Але ми можемо бачити, що вони зробили вчинок, в якому запропонували перенести concatметод із Streamкласу помічників і в нихStreams .

Відновлено до Stream.concat (Потік, Потік)

Пізніше він був переведений знову від 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, беручи вараги:public static <T> Stream<T> concat(Stream<T>... streams) { return Stream.of(streams).reduce(Stream.empty(), Stream::concat);}
MarcG

1
Сьогодні я написав свою власну версію, і саме після цього я фінансую цю тему. Підпис дещо відрізняється, але завдяки цьому він є більш загальним;) наприклад, ви можете об'єднати 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 (особистість ()) ви отримаєте помилку компіляції. Я хочу повернути Stream <T>, але заява: return return Stream.of(streams).reduce(Stream.empty(),Stream::concat)Stream <? розширює T>. (Someting <T> є підтипом Something <? розширює T>, а не іншим способом, тому його не можна буде віддати) Додатковий склад .map(identity())<? розширює T> до <T>. Це відбувається завдяки поєднанню java 8 'цільових типів' аргументів методу та типів повернення та підпису методу map (). Насправді це Функція. <T> особистість ().
кан

1
@kant Я не бачу особливого сенсу в цьому ? extends T, оскільки ви можете використовувати конверсію захоплення . У будь-якому випадку, ось мій фрагмент коду суті. Давайте продовжимо обговорення в суті.
Едвін Далорцо

12

Моя бібліотека StreamEx розширює функціональність Stream API. Зокрема , він пропонує такі методи , як Append і препенд , які вирішують цю проблему (внутрішньо вони використовують 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

Якщо ви не заперечуєте проти використання " Циклопів", реагуйте на сторонні бібліотеки має розширений тип потоку, який дозволить вам робити саме це через операторів додавання / додавання.

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

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

0

Як щодо написання власного методу конфат?

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
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.