Untuk kasus sederhana seperti yang diilustrasikan, sebagian besar sama. Namun, ada sejumlah perbedaan halus yang mungkin signifikan.
Salah satu masalah adalah dengan pemesanan. Dengan Stream.forEach
, pesanan tidak ditentukan . Itu tidak mungkin terjadi dengan aliran berurutan, masih, itu dalam spesifikasi untuk Stream.forEach
dieksekusi dalam beberapa urutan sewenang-wenang. Ini memang sering terjadi dalam aliran paralel. Sebaliknya, Iterable.forEach
selalu dieksekusi dalam urutan iterasi Iterable
, jika ada yang ditentukan.
Masalah lain adalah dengan efek samping. Tindakan yang ditentukan dalam Stream.forEach
ini harus tidak mengganggu . (Lihat dokumen paket java.util.stream .) Iterable.forEach
Berpotensi memiliki pembatasan lebih sedikit. Untuk koleksi di java.util
, Iterable.forEach
umumnya akan menggunakan koleksi itu Iterator
, yang sebagian besar dirancang untuk gagal-cepat dan yang akan melempar ConcurrentModificationException
jika koleksi tersebut secara struktural diubah selama iterasi. Namun, modifikasi yang tidak struktural yang diperbolehkan selama iterasi. Sebagai contoh, dokumentasi kelas ArrayList mengatakan "hanya menetapkan nilai elemen bukan modifikasi struktural." Demikianlah tindakan untukArrayList.forEach
diizinkan untuk menetapkan nilai di dasar ArrayList
tanpa masalah.
Koleksi bersamaan lagi-lagi berbeda. Alih-alih gagal-cepat, mereka dirancang untuk konsisten lemah . Definisi lengkap ada di tautan itu. Namun, pertimbangkan secara singkat ConcurrentLinkedDeque
. Tindakan diteruskan ke nya forEach
metode yang diperbolehkan untuk memodifikasi deque yang mendasari, bahkan struktural, dan ConcurrentModificationException
tidak pernah dilemparkan. Namun, modifikasi yang terjadi mungkin atau mungkin tidak terlihat dalam iterasi ini. (Karena itu konsistensi "lemah".)
Masih ada perbedaan lain yang terlihat jika Iterable.forEach
iterasi pada koleksi yang disinkronkan. Pada koleksi seperti itu, Iterable.forEach
ambil kunci koleksi sekali dan pegang semua panggilan ke metode tindakan. The Stream.forEach
panggilan menggunakan spliterator koleksi, yang tidak mengunci, dan yang bergantung pada aturan yang berlaku non-interferensi. Koleksi yang mendukung aliran dapat dimodifikasi selama iterasi, dan jika ya, ConcurrentModificationException
perilaku yang tidak konsisten dapat terjadi.
Iterable.forEach takes the collection's lock
. Dari mana informasi ini berasal? Saya tidak dapat menemukan perilaku seperti itu di sumber JDK.ArrayList
memiliki pemeriksaan yang cukup ketat untuk modifikasi bersamaan, dan karenanya akan sering melemparConcurrentModificationException
. Tetapi ini tidak dijamin, terutama untuk aliran paralel. Alih-alih CME Anda mungkin mendapatkan jawaban yang tidak terduga. Pertimbangkan juga modifikasi non-struktural terhadap sumber aliran. Untuk aliran paralel, Anda tidak tahu utas apa yang akan memproses elemen tertentu, atau apakah itu diproses pada saat itu dimodifikasi. Ini mengatur kondisi balapan, di mana Anda mungkin mendapatkan hasil yang berbeda di setiap lari, dan tidak pernah mendapatkan CME.Jawaban ini berkaitan dengan kinerja berbagai implementasi loop. Hanya sedikit relevan untuk loop yang disebut SANGAT SERING (seperti jutaan panggilan). Dalam kebanyakan kasus, isi dari loop adalah elemen yang paling mahal. Untuk situasi di mana Anda sering melakukan loop, ini mungkin masih menarik.
Anda harus mengulang tes ini di bawah sistem target karena ini adalah implementasi khusus, ( kode sumber lengkap ).
Saya menjalankan openjdk versi 1.8.0_111 pada mesin Linux yang cepat.
Saya menulis tes yang mengulang 10 ^ 6 kali lebih dari Daftar menggunakan kode ini dengan berbagai ukuran untuk
integers
(10 ^ 0 -> 10 ^ 5 entri).Hasilnya di bawah ini, metode tercepat bervariasi tergantung pada jumlah entri dalam daftar.
Tetapi masih dalam situasi terburuk, mengulang lebih dari 10 ^ 5 entri 10 ^ 6 kali butuh 100 detik untuk pemain terburuk, jadi pertimbangan lain lebih penting dalam hampir semua situasi.
Berikut ini adalah timing saya: milidetik / fungsi / jumlah entri dalam daftar. Setiap run adalah 10 ^ 6 loop.
Jika Anda mengulangi percobaan, saya memposting kode sumber lengkap . Harap edit jawaban ini dan tambahkan hasil Anda dengan notasi sistem yang diuji.
Menggunakan MacBook Pro, 2,5 GHz Intel Core i7, 16 GB, macOS 10.12.6:
Java 8 Hotspot VM - 3,4GHz Intel Xeon, 8 GB, Windows 10 Pro
Java 11 Hotspot VM - 3,4GHz Intel Xeon, 8 GB, Windows 10 Pro
(mesin yang sama seperti di atas, versi JDK yang berbeda)
Java 11 OpenJ9 VM - 3.4GHz Intel Xeon, 8 GB, Windows 10 Pro
(mesin yang sama dan versi JDK seperti di atas, VM yang berbeda)
Java 8 Hotspot VM - AMD 2.8GHz, 64 GB, Windows Server 2016
Java 11 Hotspot VM - AMD 2.8GHz, 64 GB, Windows Server 2016
(mesin yang sama seperti di atas, versi JDK yang berbeda)
Java 11 OpenJ9 VM - AMD 2.8GHz, 64 GB, Windows Server 2016
(versi mesin dan JDK yang sama seperti di atas, VM berbeda)
Implementasi VM yang Anda pilih juga membuat perbedaan Hotspot / OpenJ9 / etc.
sumber
Tidak ada perbedaan antara keduanya yang telah Anda sebutkan, setidaknya secara konseptual,
Collection.forEach()
itu hanya sebuah steno.Secara internal
stream()
versi memiliki overhead yang agak lebih karena penciptaan objek, tetapi melihat waktu berjalan tidak ada overhead di sana.Kedua implementasi berakhir iterasi atas
collection
konten satu kali, dan selama iterasi mencetak elemen.sumber
Stream
yang dibuat atau objek individu? AFAIK, aStream
tidak menduplikasi elemen.Collection.forEach () menggunakan iterator koleksi (jika ada yang ditentukan). Itu berarti bahwa urutan pemrosesan item didefinisikan. Sebaliknya, urutan pemrosesan Collection.stream (). ForEach () tidak ditentukan.
Dalam kebanyakan kasus, itu tidak membuat perbedaan mana dari dua yang kita pilih. Aliran paralel memungkinkan kita untuk mengeksekusi aliran di banyak utas, dan dalam situasi seperti itu, urutan eksekusi tidak ditentukan. Java hanya membutuhkan semua utas untuk menyelesaikan sebelum operasi terminal apa pun, seperti Collectors.toList (), dipanggil. Mari kita lihat contoh di mana pertama kita memanggil forach () langsung pada koleksi, dan kedua, pada aliran paralel:
Jika kita menjalankan kode beberapa kali, kita melihat bahwa list.forEach () memproses item dalam urutan penyisipan, sementara list.parallelStream (). ForEach () menghasilkan hasil yang berbeda pada setiap run. Satu kemungkinan keluaran adalah:
Yang lain adalah:
sumber