Saya membaca tentang aliran Jawa dan menemukan hal-hal baru saat saya melanjutkan. Salah satu hal baru yang saya temukan adalah peek()
fungsinya. Hampir semua yang saya baca di mengintip mengatakan itu harus digunakan untuk men-debug Streaming Anda.
Bagaimana jika saya memiliki Stream di mana setiap Akun memiliki nama pengguna, bidang kata sandi dan metode login () dan login ().
saya juga punya
Consumer<Account> login = account -> account.login();
dan
Predicate<Account> loggedIn = account -> account.loggedIn();
Mengapa ini begitu buruk?
List<Account> accounts; //assume it's been setup
List<Account> loggedInAccount =
accounts.stream()
.peek(login)
.filter(loggedIn)
.collect(Collectors.toList());
Sejauh yang saya tahu ini melakukan persis apa yang seharusnya dilakukan. Itu;
- Mengambil daftar akun
- Mencoba masuk ke setiap akun
- Menyaring semua akun yang tidak masuk
- Mengumpulkan akun yang login ke daftar baru
Apa kerugian melakukan sesuatu seperti ini? Ada alasan saya tidak melanjutkan? Terakhir, jika bukan solusi ini lalu apa?
Versi asli ini menggunakan metode .filter () sebagai berikut;
.filter(account -> {
account.login();
return account.loggedIn();
})
java
java-8
java-stream
peek
Adam.J
sumber
sumber
forEach
mungkin operasi yang Anda inginkan sebagai lawanpeek
. Hanya karena ada di API tidak berarti itu tidak terbuka untuk penyalahgunaan (sepertiOptional.of
)..peek(Account::login)
dan.filter(Account::loggedIn)
; tidak ada alasan untuk menulis Consumer and Predicate yang hanya memanggil metode lain seperti itu.forEach()
danpeek()
, hanya dapat beroperasi melalui efek samping; ini harus digunakan dengan hati-hati. ” Komentar saya lebih untuk mengingatkan bahwapeek
operasi (yang dirancang untuk tujuan debugging) tidak boleh diganti dengan melakukan hal yang sama di dalam operasi lain sepertimap()
ataufilter()
.Jawaban:
Pengambilan kunci dari ini:
Jangan menggunakan API dengan cara yang tidak disengaja, bahkan jika itu mencapai tujuan langsung Anda. Pendekatan itu mungkin pecah di masa depan, dan juga tidak jelas bagi pengelola masa depan.
Tidak ada salahnya memecah ini ke beberapa operasi, karena mereka operasi yang berbeda. Ada adalah salahnya menggunakan API dengan cara yang tidak jelas dan tidak diinginkan, yang mungkin memiliki konsekuensi jika perilaku tertentu dimodifikasi di masa depan versi Jawa.
Menggunakan
forEach
pada operasi ini akan membuat jelas kepada pengelola bahwa ada efek samping yang dimaksudkan pada setiap elemenaccounts
, dan bahwa Anda melakukan beberapa operasi yang dapat mengubahnya.Ini juga lebih konvensional dalam arti bahwa
peek
ini adalah operasi antara yang tidak beroperasi pada seluruh pengumpulan sampai operasi terminal berjalan, tetapiforEach
memang operasi terminal. Dengan cara ini, Anda dapat membuat argumen kuat di sekitar perilaku dan aliran kode Anda sebagai lawan mengajukan pertanyaan tentang apakahpeek
akan berperilaku sama sepertiforEach
dalam konteks ini.sumber
forEach
tepat di pengumpulan sumber:accounts.forEach(a -> a.login());
login()
metode mengembalikanboolean
nilai yang menunjukkan status keberhasilan ...login()
mengembalikan aboolean
, Anda dapat menggunakannya sebagai predikat yang merupakan solusi terbersih. Ini masih memiliki efek samping, tapi itu boleh saja asalkan tidak mengganggu, yaitulogin
proses` dari seseorangAccount
tidak memiliki pengaruh pada proses login` dari yang lainAccount
.Hal penting yang harus Anda pahami adalah aliran didorong oleh operasi terminal . Operasi terminal menentukan apakah semua elemen harus diproses atau tidak sama sekali. Begitu
collect
juga operasi yang memproses setiap item, sedangkanfindAny
mungkin berhenti memproses item setelah itu menemukan elemen yang cocok.Dan
count()
mungkin tidak memproses elemen apa pun ketika dapat menentukan ukuran aliran tanpa memproses item. Karena ini adalah pengoptimalan yang tidak dibuat di Java 8, tetapi yang akan di Java 9, mungkin ada kejutan ketika Anda beralih ke Java 9 dan memiliki kode yang mengandalkancount()
pemrosesan semua item. Ini juga terhubung ke detail lain yang bergantung pada implementasi, misalnya bahkan di Jawa 9, implementasi referensi tidak akan dapat memprediksi ukuran sumber stream tak terbatas dikombinasikan denganlimit
sementara tidak ada batasan mendasar yang mencegah prediksi tersebut.Karena
peek
memungkinkan "melakukan tindakan yang disediakan pada setiap elemen karena elemen dikonsumsi dari aliran yang dihasilkan ", itu tidak mengamanatkan pemrosesan elemen tetapi akan melakukan tindakan tergantung pada apa yang dibutuhkan operasi terminal. Ini menyiratkan bahwa Anda harus menggunakannya dengan sangat hati-hati jika Anda memerlukan pemrosesan tertentu, misalnya ingin menerapkan tindakan pada semua elemen. Ini berfungsi jika operasi terminal dijamin untuk memproses semua item, tetapi meskipun demikian, Anda harus yakin bahwa bukan pengembang selanjutnya yang mengubah operasi terminal (atau Anda lupa aspek halusnya).Lebih lanjut, meskipun stream menjamin untuk menjaga urutan pertemuan untuk kombinasi operasi tertentu bahkan untuk stream paralel, jaminan ini tidak berlaku untuk
peek
. Saat mengumpulkan ke dalam daftar, daftar yang dihasilkan akan memiliki urutan yang tepat untuk aliran paralel yang dipesan, tetapipeek
tindakan dapat dipanggil dalam urutan yang sewenang-wenang dan secara bersamaan.Jadi hal paling berguna yang dapat Anda lakukan
peek
adalah mencari tahu apakah elemen stream telah diproses yang persis seperti yang dikatakan dokumentasi API:sumber
Mungkin aturan praktisnya adalah bahwa jika Anda menggunakan mengintip di luar skenario "debug", Anda hanya harus melakukannya jika Anda yakin dengan apa kondisi terminasi dan penyaringan menengah. Sebagai contoh:
tampaknya menjadi kasus yang valid di mana Anda inginkan, dalam satu operasi untuk mengubah semua Foos menjadi Bar dan memberi tahu mereka semua halo.
Tampak lebih efisien dan elegan daripada sesuatu seperti:
dan Anda tidak mengulangi koleksi dua kali.
sumber
Saya akan mengatakan bahwa
peek
memberikan kemampuan untuk mendesentralisasi kode yang dapat mengubah objek streaming, atau memodifikasi keadaan global (berdasarkan pada mereka), alih-alih memasukkan semuanya ke dalam fungsi sederhana atau komposisional yang diteruskan ke metode terminal.Sekarang pertanyaannya mungkin: haruskah kita mengubah objek streaming atau mengubah status global dari dalam fungsi dalam pemrograman java gaya fungsional ?
Jika jawaban untuk salah satu dari 2 pertanyaan di atas adalah ya (atau: dalam beberapa kasus ya) maka
peek()
sudah pasti tidak hanya untuk tujuan debugging , untuk alasan yang sama yangforEach()
tidak hanya untuk tujuan debugging .Bagi saya ketika memilih antara
forEach()
danpeek()
, apakah memilih yang berikut: Apakah saya ingin potongan kode yang mengubah objek stream untuk dilampirkan ke komposer, atau apakah saya ingin mereka melampirkan langsung ke streaming?Saya pikir
peek()
akan lebih baik memasangkan dengan metode java9. misalnyatakeWhile()
mungkin perlu memutuskan kapan harus menghentikan iterasi berdasarkan objek yang sudah bermutasi, sehingga mengupasnya tidakforEach()
akan memiliki efek yang sama.PS Saya belum referensi di
map()
mana saja karena jika kita ingin bermutasi objek (atau keadaan global), daripada menghasilkan objek baru, ia berfungsi persis sepertipeek()
.sumber
Meskipun saya setuju dengan sebagian besar jawaban di atas, saya punya satu kasus di mana menggunakan mengintip sebenarnya sepertinya cara paling bersih untuk pergi.
Mirip dengan use case Anda, misalkan Anda ingin memfilter hanya pada akun aktif dan kemudian melakukan login pada akun ini.
Mengintip berguna untuk menghindari panggilan yang tidak perlu sambil tidak mengulangi koleksi dua kali:
sumber
Solusi fungsional adalah membuat objek akun tidak berubah. Jadi akun.login () harus mengembalikan objek akun baru. Ini berarti operasi peta dapat digunakan untuk login dan bukan mengintip.
sumber