Javadoc of Collector menunjukkan cara mengumpulkan elemen aliran ke Daftar baru. Apakah ada satu-liner yang menambahkan hasilnya ke dalam ArrayList yang ada?
java
java-8
java-stream
collectors
codefx
sumber
sumber
Collection
"Jawaban:
CATATAN: jawaban nosid menunjukkan cara menambahkan ke koleksi yang ada menggunakan
forEachOrdered()
. Ini adalah teknik yang berguna dan efektif untuk memutasi koleksi yang ada. Jawaban saya membahas mengapa Anda tidak boleh menggunakanCollector
untuk mengubah koleksi yang ada.Jawaban singkatnya adalah tidak , setidaknya, tidak secara umum, Anda tidak boleh menggunakan a
Collector
untuk memodifikasi koleksi yang ada.Alasannya adalah bahwa kolektor dirancang untuk mendukung paralelisme, bahkan lebih dari koleksi yang tidak aman. Cara mereka melakukan ini adalah membuat masing-masing utas beroperasi secara independen pada koleksi sendiri hasil antara. Cara setiap utas mendapatkan koleksi sendiri adalah dengan menelepon
Collector.supplier()
yang diperlukan untuk mengembalikan koleksi baru setiap kali.Koleksi-koleksi dari hasil-hasil antara ini kemudian digabungkan, sekali lagi dengan cara yang terbatas, sampai ada satu koleksi hasil tunggal. Ini adalah hasil akhir dari
collect()
operasi.Sepasang jawaban dari Balder dan assylias menyarankan untuk menggunakan
Collectors.toCollection()
dan kemudian melewati pemasok yang mengembalikan daftar yang sudah ada alih-alih daftar baru. Ini melanggar persyaratan pemasok, yaitu mengembalikan koleksi baru yang kosong setiap kali.Ini akan berfungsi untuk kasus-kasus sederhana, seperti yang ditunjukkan contoh-contoh dalam jawaban mereka. Namun, itu akan gagal, terutama jika aliran dijalankan secara paralel. (Versi perpustakaan yang akan datang mungkin berubah dalam beberapa cara yang tidak terduga yang akan menyebabkannya gagal, bahkan dalam kasus berurutan.)
Mari kita ambil contoh sederhana:
Ketika saya menjalankan program ini, saya sering mendapatkan
ArrayIndexOutOfBoundsException
. Ini karena banyak utas beroperasiArrayList
, struktur data utas tidak aman. Oke, mari kita sinkronkan:Ini tidak akan lagi gagal kecuali. Tetapi alih-alih hasil yang diharapkan:
ini memberikan hasil aneh seperti ini:
Ini adalah hasil dari operasi akumulasi / penggabungan terbatas yang saya uraikan di atas. Dengan aliran paralel, setiap utas memanggil pemasok untuk mendapatkan koleksi sendiri untuk akumulasi menengah. Jika Anda melewati pemasok yang mengembalikan koleksi yang sama , setiap utas menambahkan hasilnya ke koleksi itu. Karena tidak ada pemesanan di antara utas, hasilnya akan ditambahkan dalam beberapa urutan sewenang-wenang.
Kemudian, ketika koleksi perantara ini digabungkan, ini pada dasarnya menggabungkan daftar dengan dirinya sendiri. Daftar digabung menggunakan
List.addAll()
, yang mengatakan bahwa hasil tidak ditentukan jika kumpulan sumber dimodifikasi selama operasi. Dalam hal ini,ArrayList.addAll()
melakukan operasi array-copy, sehingga akhirnya menduplikasi sendiri, yang merupakan semacam-apa yang diharapkan, saya kira. (Perhatikan bahwa implementasi Daftar lainnya mungkin memiliki perilaku yang sama sekali berbeda.) Bagaimanapun, ini menjelaskan hasil aneh dan elemen digandakan di tujuan.Anda mungkin berkata, "Saya hanya akan memastikan untuk menjalankan streaming saya secara berurutan" dan teruskan menulis kode seperti ini
bagaimanapun. Saya akan merekomendasikan untuk tidak melakukan ini. Jika Anda mengontrol aliran, tentu saja, Anda dapat menjamin bahwa itu tidak akan berjalan secara paralel. Saya berharap bahwa gaya pemrograman akan muncul di mana aliran diberikan alih-alih koleksi. Jika seseorang memberi Anda aliran dan Anda menggunakan kode ini, itu akan gagal jika aliran itu paralel. Lebih buruk lagi, seseorang mungkin memberi Anda aliran berurutan dan kode ini akan berfungsi dengan baik untuk sementara waktu, lulus semua tes, dll. Kemudian, beberapa waktu sewenang-wenang kemudian, kode di tempat lain dalam sistem mungkin berubah menggunakan aliran paralel yang akan menyebabkan kode Anda untuk istirahat.
OK, lalu pastikan untuk mengingat untuk memanggil
sequential()
aliran apa pun sebelum Anda menggunakan kode ini:Tentu saja, Anda akan ingat untuk melakukan ini setiap waktu, bukan? :-) Katakanlah Anda lakukan. Kemudian, tim kinerja akan bertanya-tanya mengapa semua implementasi paralel yang dibuat dengan hati-hati tidak memberikan peningkatan. Dan sekali lagi mereka akan melacaknya ke kode Anda yang memaksa seluruh aliran untuk berjalan secara berurutan.
Jangan lakukan itu.
sumber
toCollection
metode ini harus mengembalikan koleksi baru dan kosong setiap kali meyakinkan saya untuk tidak melakukannya. Saya benar-benar ingin mematahkan kontrak javadoc kelas Java inti.forEachOrdered
. Efek samping termasuk menambahkan elemen ke koleksi yang ada, terlepas dari apakah itu sudah memiliki elemen. Jika Anda ingin elemen aliran ditempatkan ke koleksi baru , gunakancollect(Collectors.toList())
atautoSet()
atautoCollection()
.Sejauh yang saya bisa lihat, semua jawaban lain sejauh ini menggunakan kolektor untuk menambahkan elemen ke aliran yang ada. Namun, ada solusi yang lebih pendek, dan ini berfungsi untuk aliran sekuensial dan paralel. Anda cukup menggunakan metode untukEachOrdered dalam kombinasi dengan referensi metode.
Satu-satunya batasan adalah, bahwa sumber dan target adalah daftar yang berbeda, karena Anda tidak diizinkan untuk melakukan perubahan pada sumber aliran selama diproses.
Perhatikan bahwa solusi ini berfungsi untuk aliran sekuensial dan paralel. Namun, itu tidak mendapat manfaat dari konkurensi. Referensi metode yang diteruskan ke forEachOrdered akan selalu dieksekusi secara berurutan.
sumber
forEach(existing::add)
sebagai kemungkinan dalam jawaban dua bulan lalu . Saya seharusnya menambahkanforEachOrdered
juga ...forEachOrdered
bukanforEach
?forEachOrdered
berfungsi untuk aliran sekuensial dan paralel . Sebaliknya,forEach
mungkin menjalankan objek fungsi yang diteruskan bersamaan untuk aliran paralel. Dalam hal ini, objek fungsi harus disinkronkan dengan benar, misalnya dengan menggunakan aVector<Integer>
.target::add
. Terlepas dari thread mana metode ini dipanggil, tidak ada ras data . Saya berharap Anda tahu itu.Jawaban singkatnya adalah tidak (atau seharusnya tidak). EDIT: yeah, itu mungkin (lihat jawaban assylias di bawah), tetapi terus membaca. EDIT2: tetapi lihat jawaban Stuart Marks untuk alasan lain mengapa Anda masih tidak harus melakukannya!
Jawaban yang lebih panjang:
Tujuan konstruksi ini di Java 8 adalah untuk memperkenalkan beberapa konsep Pemrograman Fungsional ke bahasa; dalam Pemrograman Fungsional, struktur data biasanya tidak dimodifikasi, sebaliknya, yang baru dibuat dari yang lama dengan cara transformasi seperti peta, filter, lipat / perkecil dan banyak lainnya.
Jika Anda harus mengubah daftar lama, cukup kumpulkan item yang dipetakan ke dalam daftar baru:
dan kemudian lakukan
list.addAll(newList)
- lagi: jika Anda benar-benar harus.(atau buat daftar baru yang menyatukan yang lama dan yang baru, dan tetapkan kembali ke
list
variabel — ini sedikit lebih dalam semangat FP daripadaaddAll
)Mengenai API: meskipun API memperbolehkannya (sekali lagi, lihat jawaban assylias), Anda harus menghindari hal itu, setidaknya secara umum. Cara terbaik untuk tidak melawan paradigma (FP) dan mencoba mempelajarinya daripada melawannya (walaupun Jawa umumnya bukan bahasa FP), dan hanya menggunakan taktik "kotor" jika benar-benar diperlukan.
Jawaban yang sangat panjang: (yaitu jika Anda memasukkan upaya untuk benar-benar menemukan dan membaca intro / buku FP seperti yang disarankan)
Untuk mengetahui mengapa memodifikasi daftar yang ada pada umumnya merupakan ide yang buruk dan mengarah pada kode yang kurang dapat dikelola — kecuali Anda memodifikasi variabel lokal dan algoritme Anda pendek dan / atau sepele, yang berada di luar cakupan pertanyaan tentang pemeliharaan kode —Temukan pengantar yang bagus untuk Pemrograman Fungsional (ada ratusan) dan mulai membaca. Penjelasan "pratinjau" akan menjadi sesuatu seperti: itu lebih baik secara matematis dan lebih mudah untuk tidak mengubah data (di sebagian besar program Anda) dan mengarah ke tingkat yang lebih tinggi dan kurang teknis (serta lebih ramah manusia, begitu otak Anda) transisi dari definisi imperatif gaya lama) dari logika program.
sumber
Erik Allik sudah memberikan alasan yang sangat bagus, mengapa Anda kemungkinan besar tidak ingin mengumpulkan elemen aliran ke Daftar yang ada.
Bagaimanapun, Anda dapat menggunakan satu-liner berikut, jika Anda benar-benar membutuhkan fungsi ini.
Tetapi seperti yang dijelaskan Stuart Marks dalam jawabannya, Anda seharusnya tidak pernah melakukan ini, jika alirannya mungkin aliran paralel - gunakan dengan risiko Anda sendiri ...
sumber
Anda hanya perlu merujuk daftar asli Anda untuk menjadi orang yang
Collectors.toList()
mengembalikan.Ini demo:
Dan inilah cara Anda dapat menambahkan elemen yang baru dibuat ke daftar asli Anda hanya dalam satu baris.
Itulah yang disediakan Paradigma Pemrograman Fungsional ini.
sumber
targetList = sourceList.stream (). flatmap (List :: stream) .collect (Collectors.toList ());
sumber
Saya akan menggabungkan daftar lama dan daftar baru sebagai aliran dan menyimpan hasilnya ke daftar tujuan. Berfungsi secara paralel juga.
Saya akan menggunakan contoh jawaban yang diterima yang diberikan oleh Stuart Marks:
Semoga ini bisa membantu.
sumber