Mengapa Stream <T> tidak menerapkan Iterable <T>?

262

Di Java 8 kita memiliki Stream kelas <T> , yang anehnya memiliki metode

Iterator<T> iterator()

Jadi Anda akan mengharapkannya untuk mengimplementasikan antarmuka Iterable <T> , yang membutuhkan metode ini, tapi bukan itu masalahnya.

Ketika saya ingin mengulangi Stream menggunakan loop foreach, saya harus melakukan sesuatu seperti

public static Iterable<T> getIterable(Stream<T> s) {
    return new Iterable<T> {
        @Override
        public Iterator<T> iterator() {
            return s.iterator();
        }
    };
}

for (T element : getIterable(s)) { ... }

Apakah saya melewatkan sesuatu di sini?

berkeliaran
sumber
7
belum lagi bahwa 2 metode lain dari iterable (forEach dan spliterator) juga dalam Stream
njzk2
1
ini diperlukan untuk masuk Streamke API lawas yang diharapkanIterable
ZhongYu
12
IDE yang bagus (mis. IntelliJ) akan meminta Anda menyederhanakan kode Anda getIterable()menjadireturn s::iterator;
ZhongYu
24
Anda tidak perlu metode sama sekali. Di mana Anda memiliki Stream, dan ingin Iterable, cukup lewati stream :: iterator (atau, jika Anda suka, () -> stream.iterator ()), dan Anda selesai.
Brian Goetz
7
Sayangnya saya tidak bisa menulis for (T element : stream::iterator), jadi saya masih lebih suka jika Stream juga menerapkan Iterableatau metode toIterable().
Thorsten

Jawaban:

197

Orang-orang sudah menanyakan hal yang sama di milis ☺. Alasan utamanya adalah Iterable juga memiliki semantik yang dapat diulang kembali, sedangkan Stream tidak.

Saya pikir alasan utama adalah yang Iterablemenyiratkan dapat digunakan kembali, sedangkan Streamadalah sesuatu yang hanya dapat digunakan sekali - lebih seperti sebuah Iterator.

Jika Streamdiperpanjang Iterablemaka kode yang ada mungkin akan terkejut ketika menerima Iterableyang melempar yang Exceptionkedua kalinya mereka lakukan for (element : iterable).

kennytm
sumber
22
Anehnya sudah ada beberapa iterables dengan perilaku ini di Java 7, misalnya DirectoryStream: Sementara DirectoryStream memperluas Iterable, itu bukan tujuan umum Iterable karena hanya mendukung satu Iterator tunggal; menggunakan metode iterator untuk mendapatkan iterator kedua atau selanjutnya melempar IllegalStateException. ( openjdk.java.net/projects/nio/javadoc/java/nio/file/… )
roim
32
Sayangnya tidak ada dokumentasi Iterabletentang apakah iteratorharus selalu atau tidak dapat dihubungi berkali-kali. Itu sesuatu yang harus mereka masukkan ke sana. Ini tampaknya lebih merupakan praktik standar daripada spesifikasi formal.
Lii
7
Jika mereka akan menggunakan alasan, Anda akan berpikir mereka setidaknya bisa menambahkan metode asIterable () - atau kelebihan untuk semua metode yang hanya menggunakan Iterable.
Trejkaz
26
Mungkin solusi terbaik adalah membuat muka Java menerima <ter> Iterable serta berpotensi Stream <T>?
melahap elysium
3
@ Lii Ketika praktik standar berjalan, itu cukup kuat.
biziclop
161

Untuk mengonversi a Streammenjadi Iterable, Anda dapat melakukannya

Stream<X> stream = null;
Iterable<X> iterable = stream::iterator

Untuk meneruskan Streammetode yang diharapkan Iterable,

void foo(Iterable<X> iterable)

secara sederhana

foo(stream::iterator) 

namun mungkin terlihat lucu; mungkin lebih baik sedikit lebih eksplisit

foo( (Iterable<X>)stream::iterator );
ZhongYu
sumber
67
Anda juga dapat menggunakan ini dalam satu lingkaran for(X x : (Iterable<X>)stream::iterator), meskipun terlihat jelek. Sungguh, seluruh situasi itu tidak masuk akal.
Aleksandr Dubinsky
24
@HRJIntStream.range(0,N).forEach(System.out::println)
MikeFHay
11
Saya tidak mengerti sintaks usus besar dalam konteks ini. Apa perbedaan antara stream::iteratordan stream.iterator(), yang membuat yang pertama dapat diterima Iterabletetapi bukan yang kedua?
Daniel C. Sobral
20
Menjawab diri sendiri: Iterableadalah antarmuka fungsional, jadi melewati fungsi yang mengimplementasikannya sudah cukup.
Daniel C. Sobral
5
Saya harus setuju dengan @AlexandrDubinsky, Masalah sebenarnya adalah bahwa ada solusi yang simpatik untuk (X x: (Iterable <X>) stream :: iterator), yang hanya memungkinkan operasi yang sama meskipun pada kebisingan kode yang besar. Tapi kemudian ini adalah Java dan kami telah mentolerir List <String> list = ArrayList baru (Arrays.asList (array)) untuk waktu yang lama :) Meskipun kekuatan yang bisa hanya menawarkan kita semua Daftar <String> list = array. toArrayList (). Tetapi absurditas yang sebenarnya, adalah bahwa dengan mendorong semua orang untuk menggunakan forEach on Stream, pengecualian harus selalu tidak dicentang [rant berakhir].
Koordinator
10

Saya ingin menunjukkan bahwa StreamExtidak mengimplementasikan Iterable(dan Stream), serta sejumlah fungsi luar biasa lainnya yang hilang Stream.

Aleksandr Dubinsky
sumber
8

Anda dapat menggunakan Stream dalam satu forloop sebagai berikut:

Stream<T> stream = ...;

for (T x : (Iterable<T>) stream::iterator) {
    ...
}

(Jalankan cuplikan ini di sini )

(Ini menggunakan pemeran antarmuka fungsional Java 8.)

(Ini tercakup dalam beberapa komentar di atas (misalnya Aleksandr Dubinsky ), tetapi saya ingin menariknya ke dalam jawaban untuk membuatnya lebih terlihat.)

Kaya
sumber
Hampir setiap orang perlu melihatnya dua atau tiga kali sebelum mereka menyadari bagaimana itu bahkan dikompilasi. Itu tidak masuk akal. (Ini dibahas dalam tanggapan terhadap komentar dalam aliran komentar yang sama, tetapi saya ingin menambahkan komentar di sini untuk membuatnya lebih terlihat.)
ShioT
7

kennytm menjelaskan mengapa tidak aman untuk memperlakukan Streamsebagai Iterable, dan Zhong Yu menawarkan solusi yang mengizinkan penggunaan Streamsebagai Iterable, meskipun dengan cara yang tidak aman. Dimungkinkan untuk mendapatkan yang terbaik dari kedua dunia: yang dapat digunakan kembali Iterabledari Streamyang memenuhi semua jaminan yang dibuat oleh Iterablespesifikasi.

Catatan: SomeTypebukan parameter tipe di sini - Anda harus menggantinya dengan tipe yang tepat (misalnya, String) atau menggunakan refleksi

Stream<SomeType> stream = ...;
Iterable<SomeType> iterable = stream.collect(toList()):

Ada satu kelemahan utama:

Manfaat iterasi malas akan hilang. Jika Anda berencana untuk segera beralih ke semua nilai di utas saat ini, setiap overhead akan diabaikan. Namun, jika Anda berencana untuk mengulangi hanya sebagian atau di utas yang berbeda, iterasi segera dan lengkap ini dapat memiliki konsekuensi yang tidak diinginkan.

Keuntungan besar, tentu saja, adalah Anda dapat menggunakan kembali Iterable, sedangkan (Iterable<SomeType>) stream::iteratorhanya akan mengizinkan penggunaan tunggal. Jika kode penerima akan mengulangi koleksi beberapa kali, ini tidak hanya diperlukan, tetapi kemungkinan bermanfaat untuk kinerja.

Zenexer
sumber
1
Sudahkah Anda mencoba mengkompilasi kode Anda sebelum menjawab? Itu tidak bekerja.
Tagir Valeev
1
@ TarirValeev Ya, saya lakukan. Anda perlu mengganti T dengan jenis yang sesuai. Saya menyalin contoh dari kode kerja.
Zenexer
2
@ TarirValeev Saya mengujinya lagi di IntelliJ. Tampaknya IntelliJ terkadang menjadi bingung oleh sintaks ini; Saya belum benar-benar menemukan pola untuk itu. Namun, kode mengkompilasi dengan baik, dan IntelliJ menghapus pemberitahuan kesalahan setelah dikompilasi. Saya kira itu hanya bug.
Zenexer
1
Stream.toArray()mengembalikan array, bukan sebuah Iterable, jadi kode ini masih tidak dapat dikompilasi. tapi itu mungkin bug dalam gerhana, karena IntelliJ tampaknya mengkompilasinya
benez
1
@ Zenexer Bagaimana Anda bisa menetapkan array ke Iterable?
radiantRazor
3

Streamtidak menerapkan Iterable. Pemahaman umum tentang Iterableapa pun yang dapat diulangi, seringkali berulang kali.Streammungkin tidak dapat diputar ulang.

Satu-satunya solusi yang dapat saya pikirkan, di mana iterable berdasarkan aliran adalah replayable juga, adalah menciptakan kembali aliran. Saya menggunakan di Supplierbawah ini untuk membuat contoh baru aliran, setiap kali iterator baru dibuat.

    Supplier<Stream<Integer>> streamSupplier = () -> Stream.of(10);
    Iterable<Integer> iterable = () -> streamSupplier.get().iterator();
    for(int i : iterable) {
        System.out.println(i);
    }
    // Can iterate again
    for(int i : iterable) {
        System.out.println(i);
    }
Ashish Tyagi
sumber
2

Jika Anda tidak keberatan menggunakan perpustakaan pihak ketiga cyclops-react menentukan Stream yang mengimplementasikan Stream dan Iterable dan juga dapat diputar ulang (memecahkan masalah yang dijelaskan kennytm ).

 Stream<String> stream = ReactiveSeq.of("hello","world")
                                    .map(s->"prefix-"+s);

atau :-

 Iterable<String> stream = ReactiveSeq.of("hello","world")
                                      .map(s->"prefix-"+s);

 stream.forEach(System.out::println);
 stream.forEach(System.out::println);

[Pengungkapan Saya adalah pengembang utama cyclops-react]

John McClean
sumber
0

Tidak sempurna, tetapi akan berhasil:

iterable = stream.collect(Collectors.toList());

Tidak sempurna karena itu akan mengambil semua item dari aliran dan memasukkannya ke dalam itu List, yang bukan apa Iterabledan Streamtentang. Mereka seharusnya malas .

yegor256
sumber
-1

Anda dapat mengulangi semua file dalam folder menggunakan Stream<Path>seperti ini:

Path path = Paths.get("...");
Stream<Path> files = Files.list(path);

for (Iterator<Path> it = files.iterator(); it.hasNext(); )
{
    Object file = it.next();

    // ...
}
BullyWiiPlaza
sumber