Bagaimana cara mengubah iterator menjadi stream?


468

Saya mencari cara ringkas untuk mengkonversi Iterator ke Streamatau lebih khusus untuk "melihat" iterator sebagai aliran.

Untuk alasan kinerja, saya ingin menghindari salinan iterator dalam daftar baru:

Iterator<String> sourceIterator = Arrays.asList("A", "B", "C").iterator();
Collection<String> copyList = new ArrayList<String>();
sourceIterator.forEachRemaining(copyList::add);
Stream<String> targetStream = copyList.stream();

Berdasarkan beberapa saran dalam komentar, saya juga mencoba menggunakan Stream.generate:

public static void main(String[] args) throws Exception {
    Iterator<String> sourceIterator = Arrays.asList("A", "B", "C").iterator();
    Stream<String> targetStream = Stream.generate(sourceIterator::next);
    targetStream.forEach(System.out::println);
}

Namun, saya mendapatkan NoSuchElementException(karena tidak ada doa hasNext)

Exception in thread "main" java.util.NoSuchElementException
    at java.util.AbstractList$Itr.next(AbstractList.java:364)
    at Main$$Lambda$1/1175962212.get(Unknown Source)
    at java.util.stream.StreamSpliterators$InfiniteSupplyingSpliterator$OfRef.tryAdvance(StreamSpliterators.java:1351)
    at java.util.Spliterator.forEachRemaining(Spliterator.java:326)
    at java.util.stream.ReferencePipeline$Head.forEach(ReferencePipeline.java:580)
    at Main.main(Main.java:20)

Saya telah melihat StreamSupportdan Collectionstetapi saya tidak menemukan apa pun.



3
@DmitryGinzburg euh saya tidak ingin membuat "Infinite" Stream.
gontard

1
@DmitryGinzburg Stream.generate(iterator::next)bekerja?
gontard

1
@ DmitryGinzburg Itu tidak akan berfungsi untuk iterator yang terbatas.
assylias

Jawaban:


543

Salah satu caranya adalah dengan membuat Spliterator dari Iterator dan menggunakannya sebagai dasar untuk streaming Anda:

Iterator<String> sourceIterator = Arrays.asList("A", "B", "C").iterator();
Stream<String> targetStream = StreamSupport.stream(
          Spliterators.spliteratorUnknownSize(sourceIterator, Spliterator.ORDERED),
          false);

Alternatif yang mungkin lebih mudah dibaca adalah menggunakan Iterable - dan membuat Iterable dari Iterator sangat mudah dengan lambdas karena Iterable adalah antarmuka fungsional:

Iterator<String> sourceIterator = Arrays.asList("A", "B", "C").iterator();

Iterable<String> iterable = () -> sourceIterator;
Stream<String> targetStream = StreamSupport.stream(iterable.spliterator(), false);

26
Streaming malas: kode hanya menghubungkan Stream ke Iterator tetapi iterasi yang sebenarnya tidak akan terjadi sampai operasi terminal. Jika Anda menggunakan iterator sementara itu, Anda tidak akan mendapatkan hasil yang diharapkan. Misalnya Anda dapat memperkenalkan sourceIterator.next()sebelum menggunakan aliran dan Anda akan melihat efeknya (item pertama tidak akan terlihat oleh Stream).
assylias

9
@assylias, ya itu sangat bagus! Mungkin Anda bisa menjelaskan bagi pembaca di masa depan garis ajaib ini Iterable<String> iterable = () -> sourceIterator;. Saya harus mengakui bahwa saya butuh waktu untuk mengerti.
gontard

7
Saya harus memberi tahu apa yang saya temukan. Iterable<T>adalah FunctionalInterfaceyang hanya memiliki satu metode abstrak iterator(). Begitu () -> sourceIteratorjuga ekspresi lambda yang meng-instantiasi sebuah Iterableinstance sebagai implementasi anonim.
Jin Kwon

13
Sekali lagi, () -> sourceIterator;adalah bentuk singkat darinew Iterable<>() { @Override public Iterator<String> iterator() { return sourceIterator; } }
Jin Kwon

7
@ JinKwon Ini sebenarnya bukan bentuk singkat dari kelas anonim (ada beberapa perbedaan halus, seperti ruang lingkup dan bagaimana dikompilasi) tetapi itu berperilaku sama dalam kasus ini.
assylias

122

Sejak versi 21, perpustakaan Guava menyediakan Streams.stream(iterator)

Ia melakukan apa yang ditunjukkan oleh jawaban assylias .



Jauh lebih baik untuk menggunakan ini secara konsisten sampai JDK mendukung satu-liner asli. Akan jauh lebih mudah untuk menemukan (karenanya refactor) ini di masa depan daripada solusi murni-JDK yang ditunjukkan di tempat lain.
drekbour

Ini luar biasa tetapi ... bagaimana Java memiliki iterator dan stream asli ... tetapi tidak ada cara langsung yang terintegrasi untuk berpindah dari satu ke yang lain !? Cukup kelalaian menurut saya.
Dan Lenski

92

Saran bagus! Inilah pendapat saya yang dapat digunakan kembali:

public class StreamUtils {

    public static <T> Stream<T> asStream(Iterator<T> sourceIterator) {
        return asStream(sourceIterator, false);
    }

    public static <T> Stream<T> asStream(Iterator<T> sourceIterator, boolean parallel) {
        Iterable<T> iterable = () -> sourceIterator;
        return StreamSupport.stream(iterable.spliterator(), parallel);
    }
}

Dan penggunaan (pastikan untuk mengimpor asStream secara statis):

List<String> aPrefixedStrings = asStream(sourceIterator)
                .filter(t -> t.startsWith("A"))
                .collect(toList());

43

Ini dimungkinkan di Java 9.

Stream.generate(() -> null)
    .takeWhile(x -> iterator.hasNext())
    .map(n -> iterator.next())
    .forEach(System.out::println);

1
Sederhana, efisien dan tanpa menggunakan subclass - ini harus menjadi jawaban yang diterima!
martyglaubitz

1
Sayangnya ini sepertinya tidak bekerja dengan .parallel()stream. Mereka juga tampak sedikit lebih lambat daripada pergi Spliterator, bahkan untuk penggunaan berurutan.
Thomas Ahle

Juga, metode pertama muntah jika iterator kosong. Metode kedua tidak berfungsi untuk saat ini, tetapi itu melanggar persyaratan fungsi di peta dan menganggapnya stateless, jadi saya ragu melakukannya dalam kode produksi.
Hans-Peter Störr

Sungguh, ini harus menjadi jawaban yang diterima. Meskipun parallelmungkin funky, kesederhanaannya luar biasa.
Sven

11

Buat Spliteratordari Iteratormenggunakan Spliteratorskelas berisi lebih dari satu fungsi untuk membuat spliterator, misalnya di sini saya menggunakan spliteratorUnknownSizeyang mendapatkan iterator sebagai parameter, lalu buat Streaming menggunakanStreamSupport

Spliterator<Model> spliterator = Spliterators.spliteratorUnknownSize(
        iterator, Spliterator.NONNULL);
Stream<Model> stream = StreamSupport.stream(spliterator, false);

1
import com.google.common.collect.Streams;

dan gunakan Streams.stream(iterator):

Streams.stream(iterator)
       .map(v-> function(v))
       .collect(Collectors.toList());


-4

Menggunakan Collections.list(iterator).stream()...


6
Meskipun singkat, ini sangat berkinerja buruk.
Olivier Grégoire

2
Ini akan membuka seluruh iterator ke objek java lalu mengubahnya menjadi streaming. Saya tidak menyarankannya
iec2011007

3
Ini sepertinya hanya untuk enumerasi, bukan iterator.
john16384

1
Bukan jawaban yang mengerikan secara umum, berguna dalam keadaan darurat, tetapi pertanyaan itu menyebutkan kinerja dan jawabannya tidak berkinerja baik.
Kereta luncur
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.