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.

gontard
sumber
5
kemungkinan rangkap dari Bagaimana cara membuat Stream tanpa batas <E> dari Iterator <E>?
Dmitry Ginzburg
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);
assylias
sumber
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 .

numéro6
sumber
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());
Matan
sumber
43

Ini dimungkinkan di Java 9.

Stream.generate(() -> null)
    .takeWhile(x -> iterator.hasNext())
    .map(n -> iterator.next())
    .forEach(System.out::println);
PhilipRoman
sumber
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);
Bassem Reda Zohdy
sumber
1
import com.google.common.collect.Streams;

dan gunakan Streams.stream(iterator):

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

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

Israel CS Rocha
sumber
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