Menambahkan dua stream Java 8, atau elemen tambahan ke stream

168

Saya dapat menambahkan stream atau elemen tambahan, seperti ini:

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

Dan saya dapat menambahkan hal-hal baru saat saya pergi, seperti ini:

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

Tapi ini jelek, karena concatstatis. Jika concatmetode instan, contoh di atas akan lebih mudah dibaca:

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

Dan

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

Pertanyaanku adalah:

1) Apakah ada alasan bagus mengapa concatstatis? Atau adakah metode contoh yang setara yang saya lewatkan?

2) Bagaimana pun, apakah ada cara yang lebih baik untuk melakukan ini?

MarcG
sumber
4
Sepertinya hal itu tidak selalu seperti ini , tapi aku tidak bisa menemukan alasannya.
Edwin Dalorzo

Jawaban:

126

Jika Anda menambahkan impor statis untuk Stream.concat dan Stream.of , contoh pertama dapat ditulis sebagai berikut:

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

Mengimpor metode statis dengan nama generik dapat menghasilkan kode yang menjadi sulit dibaca dan dipelihara ( polusi namespace ). Jadi, mungkin lebih baik untuk membuat metode statis Anda sendiri dengan nama yang lebih bermakna. Namun, untuk demonstrasi saya akan tetap menggunakan nama ini.

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

Dengan dua metode statis ini (opsional dikombinasikan dengan impor statis), dua contoh dapat ditulis sebagai berikut:

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

Kode sekarang jauh lebih pendek. Namun, saya setuju bahwa keterbacaannya belum membaik. Jadi saya punya solusi lain.


Dalam banyak situasi, Kolektor dapat digunakan untuk memperluas fungsionalitas stream. Dengan dua Kolektor di bagian bawah, dua contoh dapat ditulis sebagai berikut:

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

Satu-satunya perbedaan antara sintaks yang Anda inginkan dan sintaks di atas adalah, bahwa Anda harus mengganti concat (...) dengan collect (concat (...)) . Dua metode statis dapat diimplementasikan sebagai berikut (opsional digunakan dalam kombinasi dengan impor statis):

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

Tentu saja ada kekurangan dengan solusi ini yang harus disebutkan. collect adalah operasi terakhir yang menghabiskan semua elemen aliran. Selain itu, concat kolektor membuat ArrayList perantara setiap kali digunakan dalam rantai. Kedua operasi dapat memiliki dampak signifikan pada perilaku program Anda. Namun, jika keterbacaan lebih penting daripada kinerja , itu mungkin masih menjadi pendekatan yang sangat membantu.

tidak ada
sumber
1
Saya tidak menemukan concatkolektor lebih mudah dibaca. Tampaknya aneh untuk memiliki metode statis parameter tunggal bernama seperti ini, dan juga digunakan collectuntuk penggabungan.
Didier L
@nosid, mungkin pertanyaan yang agak ortogonal untuk utas ini tetapi mengapa Anda mengklaim It's a bad idea to import static methods with names? Saya benar-benar tertarik - Saya merasa itu membuat kode lebih ringkas dan mudah dibaca dan banyak orang yang saya tanya berpikiran sama. Mau memberikan beberapa contoh mengapa itu umumnya buruk?
kuantum
1
@ Quantum: Apa artinya compare(reverse(getType(42)), of(6 * 9).hashCode())? Perhatikan bahwa saya tidak mengatakan bahwa impor statis adalah ide yang buruk, tetapi impor statis untuk nama generik seperti ofdan concatsedang.
nosid
1
@nosid: Tidak akan melayang di atas setiap statment dalam IDE modern dengan cepat mengungkapkan artinya? Bagaimanapun, saya pikir ini bisa menjadi pernyataan preferensi pribadi yang terbaik, karena saya masih tidak melihat alasan teknis mengapa impor statis untuk nama "generik" buruk - kecuali jika Anda menggunakan Notepad atau VI (M) untuk pemrograman dalam hal ini Anda memiliki masalah yang lebih besar.
kuantum
Saya tidak akan mengatakan bahwa Scala SDK lebih baik, tapi ... oops saya mengatakannya.
eirirlar
165

Sayangnya jawaban ini mungkin sedikit atau tidak membantu sama sekali, tetapi saya melakukan analisis forensik dari Java Lambda Mailing list untuk melihat apakah saya dapat menemukan penyebab desain ini. Inilah yang saya temukan.

Pada awalnya ada metode instance untuk Stream.concat (Stream)

Di milis saya dapat dengan jelas melihat metode awalnya diimplementasikan sebagai metode contoh, seperti yang Anda baca di utas ini oleh Paul Sandoz, tentang operasi concat.

Di dalamnya mereka membahas isu-isu yang dapat timbul dari kasus-kasus di mana aliran dapat menjadi tak terbatas dan apa arti gabungan dalam kasus-kasus itu, tetapi saya tidak berpikir itu adalah alasan untuk modifikasi.

Anda lihat di utas lain ini bahwa beberapa pengguna awal JDK 8 mempertanyakan perilaku metode instance concat ketika digunakan dengan argumen nol.

Namun, utas lain ini mengungkapkan bahwa desain metode concat sedang dalam diskusi.

Refactored ke Streams.concat (Stream, Stream)

Tetapi tanpa penjelasan apa pun, tiba-tiba, metode diubah menjadi metode statis, seperti yang dapat Anda lihat di utas ini tentang menggabungkan aliran . Ini mungkin satu-satunya utas surat yang memberi sedikit cahaya tentang perubahan ini, tetapi tidak cukup jelas bagi saya untuk menentukan alasan refactoring. Tetapi kita dapat melihat mereka melakukan komit di mana mereka menyarankan untuk memindahkan concatmetode dari Streamdan ke dalam kelas pembantu Streams.

Refactored ke Stream.concat (Stream, Stream)

Kemudian, itu dipindahkan lagi dari Streamske Stream, tetapi sekali lagi, tidak ada penjelasan untuk itu.

Jadi, intinya, alasan desainnya tidak sepenuhnya jelas bagi saya dan saya tidak dapat menemukan penjelasan yang baik. Saya kira Anda masih bisa mengajukan pertanyaan di milis.

Beberapa Alternatif untuk Rangkaian Aliran

Ini thread lain oleh Michael Hixson membahas / bertanya tentang cara-cara lain untuk menggabungkan / concat sungai

  1. Untuk menggabungkan dua aliran, saya harus melakukan ini:

    Stream.concat(s1, s2)

    bukan ini:

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

    ... Baik?

  2. Untuk menggabungkan lebih dari dua aliran, saya harus melakukan ini:

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

    bukan ini:

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

    ... Baik?

Edwin Dalorzo
sumber
6
+1 Penelitian bagus. Dan saya akan menggunakan ini sebagai Stream.concat saya mengambil varargs:public static <T> Stream<T> concat(Stream<T>... streams) { return Stream.of(streams).reduce(Stream.empty(), Stream::concat);}
MarcG
1
Hari ini saya menulis versi konser saya sendiri, dan setelah itu saya mendanai topik ini. Tanda tangan sedikit berbeda tetapi karena itu lebih umum;) misalnya Anda dapat menggabungkan Stream <Integer> dan Stream <Double> ke 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
@kant Mengapa Anda membutuhkan Function.identity()peta? Bagaimanapun, ia mengembalikan argumen yang sama dengan yang diterimanya. Ini seharusnya tidak berpengaruh pada aliran yang dihasilkan. Apakah saya melewatkan sesuatu?
Edwin Dalorzo
1
Sudahkah Anda mencoba mengetikkannya di IDE Anda? Tanpa .map (identitas ()) Anda akan mendapatkan kesalahan kompilasi. Saya ingin mengembalikan Stream <T> tetapi pernyataan: return Stream.of(streams).reduce(Stream.empty(),Stream::concat)mengembalikan Stream <? extends T>. (Suatu <T> adalah subtipe dari Sesuatu <? extends T>, bukan sebaliknya, sehingga tidak dapat dilemparkan) .map(identity())Cast tambahan <? meluas T> ke <T>. Hal ini terjadi berkat pencampuran argumen tipe metode 'java 8' dan metode pengembalian tipe dan tanda tangan map (). Sebenarnya itu Function. <T> identitas ().
kant
1
@kant Saya tidak melihat banyak gunanya melakukan ? extends T, karena Anda dapat menggunakan konversi penangkapan . Bagaimanapun, ini potongan kode intisari saya. Mari kita lanjutkan diskusi di Intisari.
Edwin Dalorzo
12

Pustaka StreamEx saya memperluas fungsionalitas Stream API. Secara khusus ia menawarkan metode seperti menambahkan dan menambahkan yang memecahkan masalah ini (secara internal mereka gunakan concat). Metode-metode ini dapat menerima aliran lain atau koleksi atau array varargs. Menggunakan perpustakaan saya masalah Anda dapat diselesaikan dengan cara ini (perhatikan bahwa x != 0terlihat aneh untuk aliran non-primitif):

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

Omong-omong, ada juga jalan pintas untuk filteroperasi Anda :

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

Kerjakan saja:

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

di mana identity()impor statis Function.identity().

Menggabungkan beberapa aliran ke dalam satu aliran sama dengan meratakan aliran.

Namun, sayangnya, untuk beberapa alasan tidak ada flatten()metode aktif Stream, jadi Anda harus menggunakan flatMap()fungsi identitas.

herman
sumber
1

Jika Anda tidak keberatan menggunakan Perpustakaan Pihak Ketiga cyclops-react memiliki tipe Stream tambahan yang akan memungkinkan Anda melakukan hal itu melalui operator append / prepend.

Nilai individual, array, iterables, Streaming, atau aliran reaktif. Penerbit dapat ditambahkan dan ditambahkan sebagai metode instan.

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

[Pengungkapan Saya adalah pengembang utama cyclops-react]

John McClean
sumber
1

Pada akhirnya saya tidak tertarik untuk menggabungkan aliran, tetapi untuk mendapatkan hasil gabungan dari pemrosesan setiap elemen dari semua aliran tersebut.

Meskipun menggabungkan aliran mungkin rumit (sehingga utas ini), menggabungkan hasil pemrosesan mereka cukup mudah.

Kunci untuk menyelesaikannya adalah membuat kolektor Anda sendiri dan memastikan bahwa fungsi pemasok untuk kolektor baru mengembalikan koleksi yang sama setiap kali ( bukan yang baru ), kode di bawah ini menggambarkan pendekatan ini.

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
sumber
0

Bagaimana dengan menulis metode konser Anda sendiri?

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

Setidaknya ini membuat contoh pertama Anda jauh lebih mudah dibaca.

Felix S
sumber
1
Gunakan hati-hati saat membangun aliran dari rangkaian berulang. Mengakses elemen dari aliran yang digabungkan secara mendalam dapat menghasilkan rantai panggilan yang dalam, atau bahkan StackOverflowError.
Legna