Kapan Anda akan menggunakan collect()
vs reduce()
? Adakah yang punya contoh konkret yang baik tentang kapan lebih baik pergi ke satu arah atau yang lain?
Javadoc menyebutkan bahwa mengumpulkan () adalah pengurangan yang bisa berubah .
Mengingat bahwa ini adalah pengurangan yang bisa berubah, saya menganggap itu memerlukan sinkronisasi (secara internal) yang, pada gilirannya, dapat merusak kinerja. Agaknya reduce()
lebih mudah diparalelkan dengan biaya harus membuat struktur data baru untuk kembali setelah setiap langkah dalam pengurangan.
Pernyataan di atas hanyalah dugaan dan saya ingin ahli untuk berpadu di sini.
java
java-8
java-stream
jimhooker2002
sumber
sumber
Jawaban:
reduce
adalah operasi " lipat ", itu berlaku operator biner untuk setiap elemen dalam aliran di mana argumen pertama ke operator adalah nilai kembali dari aplikasi sebelumnya dan argumen kedua adalah elemen aliran saat ini.collect
adalah operasi agregasi di mana "koleksi" dibuat dan setiap elemen "ditambahkan" ke koleksi itu. Koleksi di berbagai bagian aliran kemudian ditambahkan bersama.The dokumen Anda terhubung memberi alasan untuk memiliki dua pendekatan yang berbeda:
Jadi intinya adalah bahwa parallelisation adalah sama dalam kedua kasus tetapi dalam
reduce
kasus ini kita menerapkan fungsi ke elemen aliran itu sendiri. Dalam halcollect
ini kita menerapkan fungsi ke wadah yang bisa berubah.sumber
int
tidak dapat diubah sehingga Anda tidak dapat dengan mudah menggunakan operasi pengumpulan. Anda bisa melakukan peretasan kotor seperti menggunakanAtomicInteger
atau beberapa kebiasaanIntWrapper
tetapi mengapa Anda melakukannya? Operasi lipatan sangat berbeda dengan operasi pengumpulan.reduce
metode lain , di mana Anda bisa mengembalikan objek bertipe berbeda dari elemen stream.Alasannya sederhana:
collect()
hanya dapat bekerja dengan objek hasil yang bisa berubah .reduce()
adalah dirancang untuk bekerja dengan berubah benda hasil."
reduce()
dengan abadi" contohcollect()
Contoh " dengan bisa berubah"Misal, jika Anda ingin menghitung jumlah secara manual, menggunakannya
collect()
tidak dapat bekerja denganBigDecimal
tetapi hanya denganMutableInt
dariorg.apache.commons.lang.mutable
misalnya. Lihat:Ini bekerja karena akumulator
container.add(employee.getSalary().intValue());
tidak seharusnya mengembalikan objek baru dengan hasil tetapi untuk mengubah keadaan bisa berubahcontainer
dari jenisMutableInt
.Jika Anda ingin menggunakan
BigDecimal
sebagai gantinyacontainer
Anda tidak dapat menggunakancollect()
metode karenacontainer.add(employee.getSalary());
tidak akan mengubahcontainer
karenaBigDecimal
tidak dapat diubah. (Terlepas dari iniBigDecimal::new
tidak akan berfungsi karenaBigDecimal
tidak memiliki konstruktor kosong)sumber
Integer
konstruktor (new Integer(6)
), yang tidak digunakan lagi di versi Java yang lebih baru.Integer.valueOf(6)
StringBuilder
yang bisa berubah. Lihat: hg.openjdk.java.net/jdk8/jdk8/jdk/file/687fd7c7986d/src/share/…Reduksi normal dimaksudkan untuk menggabungkan dua nilai yang tidak berubah seperti int, dobel, dll. Dan menghasilkan yang baru; ini adalah pengurangan yang tidak berubah . Sebaliknya, metode kumpulkan dirancang untuk bermutasi wadah untuk mengakumulasikan hasil yang seharusnya dihasilkan.
Untuk mengilustrasikan masalah, misalkan Anda ingin mencapai
Collectors.toList()
menggunakan pengurangan sederhana sepertiIni setara dengan
Collectors.toList()
. Namun, dalam hal ini Anda memutasikanList<Integer>
. Seperti yang kita ketahui,ArrayList
ini bukan thread-safe, juga tidak aman untuk menambah / menghapus nilai dari itu saat iterasi sehingga Anda akan mendapatkan pengecualian bersamaan atauArrayIndexOutOfBoundsException
atau segala jenis pengecualian (terutama ketika dijalankan secara paralel) ketika Anda memperbarui daftar atau penggabung mencoba untuk menggabungkan daftar karena Anda mengubah daftar dengan mengakumulasi (menambahkan) bilangan bulat ke dalamnya. Jika Anda ingin membuat utas ini aman, Anda harus memberikan daftar baru setiap kali yang akan mengganggu kinerja.Sebaliknya,
Collectors.toList()
karya - karyanya serupa. Namun, itu menjamin keamanan utas saat Anda mengakumulasi nilai-nilai ke dalam daftar. Dari dokumentasi untukcollect
metode ini :Jadi, untuk menjawab pertanyaanmu:
jika Anda memiliki nilai-nilai abadi seperti
ints
,doubles
,Strings
maka pengurangan biasa bekerja dengan baik. Namun, jika Anda harusreduce
nilai-nilai Anda ke katakanlahList
(struktur data bisa berubah) maka Anda perlu menggunakan pengurangan bisa berubah dengancollect
metode ini.sumber
x
utas, masing-masing "menambah identitas" kemudian menggabungkan bersama. Contoh yang baik.public static void main(String[] args) { List<Integer> l = new ArrayList<>(); l.add(1); l.add(10); l.add(3); l.add(-3); l.add(-4); List<Integer> numbers = l.stream().reduce( new ArrayList<Integer>(), (List<Integer> l2, Integer e) -> { l2.add(e); return l2; }, (List<Integer> l1, List<Integer> l2) -> { l1.addAll(l2); return l1; });for(Integer i:numbers)System.out.println(i); } }
saya mencoba dan tidak mendapatkan pengecualian CCmBiarkan aliran menjadi <- b <- c <- d
Dalam pengurangan,
Anda akan memiliki ((a # b) # c) # d
di mana # adalah operasi yang menarik yang ingin Anda lakukan.
Dalam koleksi,
kolektor Anda akan memiliki semacam struktur pengumpulan K.
K mengkonsumsi a. K kemudian mengkonsumsi b. K kemudian mengkonsumsi c. K kemudian mengkonsumsi d.
Pada akhirnya, Anda bertanya pada K apa hasil akhirnya.
K kemudian memberikannya kepada Anda.
sumber
Mereka sangat berbeda dalam jejak memori potensial selama runtime. Saat
collect()
mengumpulkan dan menempatkan semua data ke dalam koleksi,reduce()
secara eksplisit meminta Anda untuk menentukan cara mengurangi data yang membuatnya melalui aliran.Misalnya, jika Anda ingin membaca beberapa data dari file, memprosesnya, dan memasukkannya ke dalam database, Anda mungkin berakhir dengan kode aliran java yang mirip dengan ini:
Dalam hal ini, kami menggunakan
collect()
untuk memaksa java untuk melakukan streaming data dan membuatnya menyimpan hasilnya ke dalam database. Tanpacollect()
data tidak pernah dibaca dan tidak pernah disimpan.Kode ini dengan senang hati menghasilkan
java.lang.OutOfMemoryError: Java heap space
kesalahan runtime, jika ukuran file cukup besar atau ukuran tumpukan cukup rendah. Alasan yang jelas adalah bahwa ia mencoba untuk menumpuk semua data yang membuatnya melalui aliran (dan, pada kenyataannya, telah disimpan dalam database) ke dalam koleksi yang dihasilkan dan ini memecah tumpukan.Namun, jika Anda mengganti
collect()
denganreduce()
- itu tidak akan menjadi masalah lagi karena yang terakhir akan mengurangi dan membuang semua data yang berhasil melaluinya.Dalam contoh yang disajikan, ganti saja
collect()
dengan sesuatu denganreduce
:Anda bahkan tidak perlu peduli untuk membuat perhitungan tergantung pada
result
Java bukan bahasa FP (pemrograman fungsional) murni dan tidak dapat mengoptimalkan data yang tidak digunakan di bagian bawah aliran karena kemungkinan efek samping .sumber
System.out.println (jumlah);
Mengurangi fungsi menangani dua parameter, parameter pertama adalah nilai pengembalian sebelumnya ke dalam aliran, parameter kedua adalah nilai penghitungan arus dalam arus, itu menjumlahkan nilai pertama dan nilai saat ini sebagai nilai pertama dalam perhitungan berikutnya.
sumber
Menurut dokumen
Jadi pada dasarnya Anda
reducing()
hanya akan menggunakannya saat dipaksa di dalam koleksi. Ini contoh lain :Menurut tutorial ini mengurangi kadang-kadang kurang efisien
Jadi identitasnya "digunakan kembali" dalam skenario pengurangan, jadi sedikit lebih efisien untuk digunakan
.reduce
jika memungkinkan.sumber