Mengapa Iterable <T> tidak menyediakan metode stream () dan parallelStream ()?

241

Saya bertanya-tanya mengapa Iterableantarmuka tidak menyediakan stream()dan parallelStream()metode. Pertimbangkan kelas berikut:

public class Hand implements Iterable<Card> {
    private final List<Card> list = new ArrayList<>();
    private final int capacity;

    //...

    @Override
    public Iterator<Card> iterator() {
        return list.iterator();
    }
}

Ini adalah implementasi dari Tangan karena Anda dapat memiliki kartu di tangan Anda saat memainkan Permainan Kartu Perdagangan.

Pada dasarnya itu membungkus List<Card>, memastikan kapasitas maksimum dan menawarkan beberapa fitur berguna lainnya. Lebih baik menerapkannya secara langsung sebagai List<Card>.

Sekarang, untuk kenyamanan saya pikir akan lebih baik untuk diimplementasikan Iterable<Card>, sehingga Anda dapat menggunakan loop for-ditingkatkan jika Anda ingin mengulanginya. ( HandKelas saya juga menyediakan get(int index)metode, oleh karena Iterable<Card>itu dibenarkan menurut pendapat saya.)

The Iterableantarmuka menyediakan berikut (ditinggalkan javadoc):

public interface Iterable<T> {
    Iterator<T> iterator();

    default void forEach(Consumer<? super T> action) {
        Objects.requireNonNull(action);
        for (T t : this) {
            action.accept(t);
        }
    }

    default Spliterator<T> spliterator() {
        return Spliterators.spliteratorUnknownSize(iterator(), 0);
    }
}

Sekarang Anda dapat memperoleh aliran dengan:

Stream<Hand> stream = StreamSupport.stream(hand.spliterator(), false);

Jadi pertanyaan sebenarnya:

  • Mengapa Iterable<T>tidak menyediakan metode default yang menerapkan stream()dan parallelStream(), saya tidak melihat apa pun yang membuat ini tidak mungkin atau tidak diinginkan?

Pertanyaan terkait yang saya temukan adalah sebagai berikut: Mengapa Stream <T> tidak mengimplementasikan Iterable <T>?
Yang anehnya cukup menyarankan untuk melakukannya agak sebaliknya.

skiwi
sumber
1
Saya kira ini adalah pertanyaan yang bagus untuk Mailing List Lambda .
Edwin Dalorzo
Mengapa aneh untuk ingin beralih di atas aliran? Bagaimana lagi Anda bisa break;melakukan iterasi? (Ok, Stream.findFirst()mungkin solusi, tapi itu mungkin tidak memenuhi semua kebutuhan ...)
glglgl
Lihat juga Konversi Iterable ke Stream menggunakan Java 8 JDK untuk solusi praktis.
Vadzim

Jawaban:

298

Ini bukan kelalaian; ada diskusi terperinci tentang daftar EG pada Juni 2013.

Diskusi definitif dari Kelompok Pakar berakar di utas ini .

Sementara itu tampak "jelas" (bahkan untuk Kelompok Ahli, pada awalnya) yang stream()tampaknya masuk akal Iterable, fakta yang Iterablebegitu umum menjadi masalah, karena tanda tangan yang jelas:

Stream<T> stream()

tidak selalu seperti yang Anda inginkan. Beberapa hal yang Iterable<Integer>lebih suka metode aliran mereka mengembalikan IntStream, misalnya. Tetapi menempatkan stream()metode ini setinggi-tingginya dalam hierarki akan membuat itu tidak mungkin. Jadi sebagai gantinya, kami membuatnya sangat mudah untuk membuat Streamdari Iterable, dengan menyediakan spliterator()metode. Implementasi stream()dalam Collectionhanyalah:

default Stream<E> stream() {
    return StreamSupport.stream(spliterator(), false);
}

Setiap klien dapat memperoleh aliran yang mereka inginkan dari Iterabledengan:

Stream s = StreamSupport.stream(iter.spliterator(), false);

Pada akhirnya kami menyimpulkan bahwa menambahkan stream()untuk Iterableakan menjadi suatu kesalahan.

Brian Goetz
sumber
8
Saya mengerti, pertama-tama, terima kasih banyak untuk menjawab pertanyaan. Saya masih penasaran mengapa mengapa Iterable<Integer>(saya pikir Anda bicarakan?) Ingin mengembalikan sebuah IntStream. Apakah iterable maka tidak suka menjadi PrimitiveIterator.OfInt? Atau mungkin maksud Anda usecase lain?
skiwi
139
Saya merasa aneh bahwa logika di atas seharusnya diterapkan pada Iterable (saya tidak dapat memiliki streaming () karena seseorang mungkin ingin mengembalikan IntStream) sedangkan jumlah pemikiran yang sama tidak diberikan untuk menambahkan metode yang sama persis ke Collection ( Saya mungkin ingin aliran <Integer> Koleksi saya () untuk mengembalikan IntStream juga.) Entah itu hadir pada keduanya atau tidak ada pada keduanya, orang mungkin hanya akan melanjutkan hidup mereka, tetapi karena itu ada di satu dan tidak ada di yang lain, itu menjadi penghilangan yang cukup mencolok ...
Trejkaz
6
Brian McCutchon: Itu lebih masuk akal bagi saya. Sepertinya orang-orang bosan berdebat dan memutuskan untuk bermain aman.
Jonathan Locke
44
Walaupun ini masuk akal, apakah ada alasan mengapa tidak ada alternatif statis Stream.of(Iterable), yang setidaknya akan membuat metode ini dapat ditemukan dengan membaca dokumentasi API - sebagai seseorang yang tidak pernah benar-benar bekerja dengan internal Streams yang tidak pernah saya lakukan. bahkan tampak di StreamSupport, yang dijelaskan dalam dokumentasi menyediakan "operasi tingkat rendah" yang "sebagian besar untuk penulis perpustakaan".
Jules
9
Saya sangat setuju dengan Jules. metode statis Stream.of (Iteratable iter) atau Stream.of (Iterator iter) harus ditambahkan, alih-alih StreamSupport.stream (iter.spliterator (), false);
user_3380739
23

Saya melakukan penyelidikan di beberapa milis proyek lambda dan saya pikir saya menemukan beberapa diskusi yang menarik.

Sejauh ini saya belum menemukan penjelasan yang memuaskan. Setelah membaca semua ini, saya menyimpulkan bahwa itu hanyalah kelalaian. Tetapi Anda dapat melihat di sini bahwa itu telah dibahas beberapa kali selama bertahun-tahun selama desain API.

Lambda Libs Spec Ahli

Saya menemukan diskusi tentang ini di mailing list Lambda Libs Spec Experts :

Di bawah Iterable / Iterator.stream () Sam Pullara berkata:

Saya bekerja dengan Brian untuk melihat bagaimana fungsi limit / subtream [1] dapat diimplementasikan dan ia menyarankan konversi ke Iterator adalah cara yang tepat untuk melakukannya. Saya telah memikirkan solusi itu tetapi tidak menemukan cara yang jelas untuk mengambil iterator dan mengubahnya menjadi aliran. Ternyata itu ada di sana, Anda hanya perlu mengkonversi iterator menjadi spliterator dan kemudian mengubah spliterator menjadi stream. Jadi ini membawa saya untuk meninjau kembali apakah kita harus memiliki ini menggantung salah satu Iterable / Iterator secara langsung atau keduanya.

Saran saya adalah untuk setidaknya memilikinya di Iterator sehingga Anda dapat bergerak dengan bersih antara dua dunia dan itu juga akan mudah ditemukan daripada harus melakukan:

Streams.stream (Spliterators.spliteratorUnknownSize (iterator, Spliterator.ORDERED))

Dan kemudian Brian Goetz merespons :

Saya pikir maksud Sam adalah bahwa ada banyak kelas perpustakaan yang memberi Anda Iterator tetapi jangan biarkan Anda menulis spliterator Anda sendiri. Jadi yang bisa Anda lakukan adalah panggilan stream (spliteratorUnknownSize (iterator)). Sam menyarankan agar kami mendefinisikan Iterator.stream () untuk melakukannya untuk Anda.

Saya ingin menjaga metode stream () dan spliterator () sebagai penulis perpustakaan / pengguna tingkat lanjut.

Dan kemudian

"Mengingat bahwa menulis Spliterator lebih mudah daripada menulis Iterator, saya lebih suka menulis Spliterator daripada Iterator (Iterator sangat 90-an :)"

Kamu tidak mengerti intinya. Ada zillions kelas di luar sana yang sudah memberi Anda Iterator. Dan banyak dari mereka tidak siap spliterator.

Diskusi Sebelumnya di Mailing List Lambda

Ini mungkin bukan jawaban yang Anda cari tetapi dalam milis Project Lambda ini dibahas secara singkat. Mungkin ini membantu mendorong diskusi yang lebih luas tentang masalah ini.

Dalam kata-kata Brian Goetz di bawah Streams from Iterable :

Melangkah mundur ...

Ada banyak cara untuk membuat Stream. Semakin banyak informasi yang Anda miliki tentang cara mendeskripsikan elemen, semakin banyak fungsi dan kinerja yang dapat diberikan perpustakaan aliran. Dalam urutan paling tidak untuk sebagian besar informasi, mereka adalah:

Iterator

Iterator + ukuran

Spliterator

Spliterator yang tahu ukurannya

Spliterator yang mengetahui ukurannya, dan selanjutnya mengetahui bahwa semua sub-pemisahan mengetahui ukurannya.

(Beberapa orang mungkin terkejut menemukan bahwa kita dapat mengekstraksi paralelisme bahkan dari iterator yang bodoh dalam kasus-kasus di mana Q (pekerjaan per elemen) adalah non-trivial.)

Jika Iterable memiliki metode stream (), itu hanya akan membungkus Iterator dengan Spliterator, tanpa informasi ukuran. Tapi, kebanyakan hal-hal yang Iterable lakukan memiliki informasi ukuran. Yang berarti kami melayani aliran yang kurang. Itu tidak begitu baik.

Salah satu kelemahan dari praktik API yang digariskan oleh Stephen di sini, yaitu menerima Iterable dan bukannya Collection, adalah bahwa Anda memaksa hal-hal melalui "pipa kecil" dan karena itu membuang informasi ukuran ketika itu mungkin berguna. Tidak apa-apa jika semua yang Anda lakukan adalah untuk melakukannya, tetapi jika Anda ingin berbuat lebih banyak, lebih baik jika Anda dapat menyimpan semua informasi yang Anda inginkan.

Default yang disediakan oleh Iterable akan menjadi jelek - itu akan membuang ukuran meskipun sebagian besar Iterables tahu informasi itu.

Kontradiksi?

Meskipun, sepertinya diskusi didasarkan pada perubahan yang dilakukan Kelompok Pakar terhadap desain awal Streams yang awalnya didasarkan pada iterator.

Meski begitu, menarik untuk diperhatikan bahwa di antarmuka seperti Collection, metode stream didefinisikan sebagai:

default Stream<E> stream() {
   return StreamSupport.stream(spliterator(), false);
}

Kode yang persis sama dengan yang digunakan dalam antarmuka Iterable.

Jadi, inilah mengapa saya mengatakan jawaban ini mungkin tidak memuaskan, tetapi masih menarik untuk dibahas.

Bukti Refactoring

Melanjutkan analisis di milis, sepertinya metode splitIterator pada awalnya berada di antarmuka Collection, dan pada beberapa titik di 2013 mereka memindahkannya ke Iterable.

Tarik splitIterator dari Koleksi ke Iterable .

Kesimpulan / Teori?

Maka kemungkinannya adalah bahwa kurangnya metode di Iterable hanya kelalaian, karena sepertinya mereka seharusnya telah memindahkan metode aliran juga ketika mereka memindahkan splitIterator dari Collection ke Iterable.

Jika ada alasan lain, itu tidak jelas. Ada yang punya teori lain?

Edwin Dalorzo
sumber
Saya menghargai tanggapan Anda, tetapi saya tidak setuju dengan alasan di sana. Pada saat itu Anda menimpa spliterator()dari Iterable, maka semua masalah ada tetap, dan Anda dapat sepele menerapkan stream()dan parallelStream()..
skiwi
@skiwi Itu sebabnya saya mengatakan ini mungkin bukan jawabannya. Saya hanya mencoba menambah diskusi, karena sulit untuk mengetahui mengapa kelompok ahli membuat keputusan yang mereka lakukan. Saya kira yang bisa kita lakukan hanyalah mencoba melakukan forensik di milis dan melihat apakah kita dapat menemukan alasannya.
Edwin Dalorzo
1
@skiwi Saya meninjau milis lain dan menemukan lebih banyak bukti untuk diskusi dan mungkin beberapa ide yang membantu untuk berteori beberapa diagnosis.
Edwin Dalorzo
Terima kasih atas upaya Anda, saya harus benar-benar belajar cara membagi milis tersebut secara efisien. Akan membantu jika mereka dapat divisualisasikan dalam beberapa ... cara modern, seperti forum atau sesuatu, karena membaca email teks biasa dengan kutipan di dalamnya tidak sepenuhnya efisien.
skiwi
6

Jika Anda tahu ukuran yang bisa Anda gunakan java.util.Collectionyang menyediakan stream()metode ini:

public class Hand extends AbstractCollection<Card> {
   private final List<Card> list = new ArrayList<>();
   private final int capacity;

   //...

   @Override
   public Iterator<Card> iterator() {
       return list.iterator();
   }

   @Override
   public int size() {
      return list.size();
   }
}

Lalu:

new Hand().stream().map(...)

Saya menghadapi masalah yang sama dan terkejut bahwa Iterableimplementasi saya dapat dengan mudah diperluas ke AbstractCollectionimplementasi hanya dengan menambahkan size()metode (untungnya saya memiliki ukuran koleksi :-)

Anda juga harus mempertimbangkan untuk mengganti Spliterator<E> spliterator().

Udo
sumber