Saya memiliki daftar myListToParse
tempat saya ingin memfilter elemen dan menerapkan metode pada setiap elemen, dan menambahkan hasilnya di daftar lain myFinalList
.
Dengan Java 8 saya perhatikan bahwa saya dapat melakukannya dengan 2 cara berbeda. Saya ingin tahu cara yang lebih efisien di antara mereka dan memahami mengapa satu cara lebih baik daripada yang lain.
Saya terbuka untuk saran tentang cara ketiga.
Metode 1:
myFinalList = new ArrayList<>();
myListToParse.stream()
.filter(elt -> elt != null)
.forEach(elt -> myFinalList.add(doSomething(elt)));
Metode 2:
myFinalList = myListToParse.stream()
.filter(elt -> elt != null)
.map(elt -> doSomething(elt))
.collect(Collectors.toList());
java
java-8
java-stream
Emilien Brigand
sumber
sumber
elt -> elt != null
dapat diganti denganObjects::nonNull
Optional<T>
sebaliknya dalam kombinasi denganflatMap
..map(this::doSomething)
dengan asumsi itudoSomething
adalah metode yang tidak statis. Jika statis, Anda dapat menggantithis
dengan nama kelas.Jawaban:
Jangan khawatir tentang perbedaan kinerja, mereka akan menjadi minimal dalam hal ini secara normal.
Metode 2 lebih disukai karena
itu tidak memerlukan mutasi koleksi yang ada di luar ekspresi lambda,
itu lebih mudah dibaca karena langkah-langkah berbeda yang dilakukan dalam pipa koleksi ditulis secara berurutan: pertama operasi filter, kemudian operasi peta, kemudian mengumpulkan hasilnya (untuk info lebih lanjut tentang manfaat pipa koleksi, lihat artikel bagus Martin Fowler ),
Anda dapat dengan mudah mengubah cara nilai dikumpulkan dengan mengganti
Collector
yang digunakan. Dalam beberapa kasus Anda mungkin perlu menulis sendiriCollector
, tetapi manfaatnya adalah Anda dapat dengan mudah menggunakannya kembali.sumber
Saya setuju dengan jawaban yang ada bahwa bentuk kedua lebih baik karena tidak memiliki efek samping dan lebih mudah disejajarkan (cukup gunakan aliran paralel).
Dari segi kinerja, tampaknya keduanya setara hingga Anda mulai menggunakan aliran paralel. Dalam hal ini, peta akan tampil jauh lebih baik. Lihat di bawah hasil patokan mikro :
Anda tidak dapat meningkatkan contoh pertama dengan cara yang sama karena forEach adalah metode terminal - ia mengembalikan batal - jadi Anda terpaksa menggunakan lambda stateful. Tapi itu benar-benar ide yang buruk jika Anda menggunakan aliran paralel .
Akhirnya perhatikan bahwa cuplikan kedua Anda dapat ditulis dengan cara yang lebih ringkas dengan referensi metode dan impor statis:
sumber
Salah satu manfaat utama menggunakan stream adalah memberikan aliran untuk memproses data dengan cara deklaratif, yaitu menggunakan gaya pemrograman fungsional. Ini juga memberikan kemampuan multi-threading untuk makna gratis, tidak perlu menulis kode multi-utas tambahan untuk membuat aliran Anda bersamaan.
Dengan asumsi alasan Anda menjelajahi gaya pemrograman ini adalah bahwa Anda ingin mengeksploitasi manfaat ini maka sampel kode pertama Anda berpotensi tidak berfungsi karena
foreach
metode ini digolongkan sebagai terminal (artinya dapat menghasilkan efek samping).Cara kedua lebih disukai dari sudut pandang pemrograman fungsional karena fungsi peta dapat menerima fungsi lambda stateless. Lebih eksplisit lagi, lambda yang diteruskan ke fungsi peta seharusnya
ArrayList
).Manfaat lain dengan pendekatan kedua adalah jika aliran paralel dan kolektor bersamaan dan tidak berurutan maka karakteristik ini dapat memberikan petunjuk yang berguna untuk operasi pengurangan untuk melakukan pengumpulan secara bersamaan.
sumber
Jika Anda menggunakan Eclipse Collections, Anda dapat menggunakan
collectIf()
metode ini.Ini mengevaluasi dengan bersemangat dan harus sedikit lebih cepat daripada menggunakan Stream.
Catatan: Saya pengendara untuk Eclipse Collections.
sumber
Saya lebih suka cara kedua.
Ketika Anda menggunakan cara pertama, jika Anda memutuskan untuk menggunakan aliran paralel untuk meningkatkan kinerja, Anda tidak akan memiliki kontrol atas urutan di mana elemen akan ditambahkan ke daftar output oleh
forEach
.Saat Anda menggunakan
toList
, Streams API akan mempertahankan pesanan meskipun Anda menggunakan aliran paralel.sumber
forEachOrdered
alih-alihforEach
jika dia ingin menggunakan aliran paralel tetapi tetap mempertahankan pesanan. Tetapi sebagai dokumentasi untukforEach
negara, menjaga tatanan perjumpaan mengorbankan manfaat paralelisme. Saya menduga itu juga halnya dengantoList
itu.Ada opsi ketiga - menggunakan
stream().toArray()
- lihat komentar di bawah mengapa streaming tidak memiliki metode toList . Ternyata lebih lambat daripada forEach () atau collect (), dan kurang ekspresif. Mungkin dioptimalkan di JDK build selanjutnya, jadi menambahkannya di sini untuk berjaga-jaga.asumsi
List<String>
dengan tolok ukur mikro-mikro, entri 1M, 20% nol, dan transformasi sederhana di doSomething ()
hasilnya
paralel:
sekuensial:
sejajar tanpa nulls dan filter (demikian juga arusnya
SIZED
): toArrays memiliki kinerja terbaik dalam kasus seperti itu, dan.forEach()
gagal dengan "indexOutOfBounds" pada ArrayList penerima, harus diganti dengan.forEachOrdered()
sumber
Mungkin Metode 3.
Saya selalu lebih suka memisahkan logika.
sumber
Jika menggunakan 3rd Pary Libaries ok cyclops-react mendefinisikan koleksi Lazy yang diperluas dengan fungsi ini. Misalnya, kita cukup menulis
ListX myListToParse;
ListX myFinalList = myListToParse.filter (elt -> elt! = Null) .map (elt -> doSomething (elt));
myFinalList tidak dievaluasi sampai akses pertama (dan setelah daftar terwujud di-cache dan digunakan kembali).
[Pengungkapan Saya adalah pengembang utama cyclops-react]
sumber