Kerangka kerja Java 8 stream baru dan teman-teman membuat beberapa kode java yang sangat ringkas, tapi saya telah menemukan situasi yang tampaknya sederhana yang sulit dilakukan secara ringkas.
Pertimbangkan a List<Thing> things
dan metode Optional<Other> resolve(Thing thing)
. Saya ingin memetakan Thing
s ke Optional<Other>
s dan mendapatkan yang pertama Other
. Solusi yang jelas akan digunakan things.stream().flatMap(this::resolve).findFirst()
, tetapi flatMap
mengharuskan Anda mengembalikan aliran, dan Optional
tidak memiliki stream()
metode (atau itu Collection
atau menyediakan metode untuk mengubahnya atau melihatnya sebagai Collection
).
Yang terbaik yang bisa saya pikirkan adalah ini:
things.stream()
.map(this::resolve)
.filter(Optional::isPresent)
.map(Optional::get)
.findFirst();
Tapi itu sepertinya bertele-tele untuk kasus yang sangat umum. Adakah yang punya ide yang lebih baik?
sumber
.flatMap(Optional::toStream)
, dengan versi Anda, Anda benar-benar melihat apa yang terjadi.Optional.stream
ada di JDK 9 sekarang ....Jawaban:
Jawa 9
Optional.stream
telah ditambahkan ke JDK 9. Ini memungkinkan Anda untuk melakukan hal berikut, tanpa perlu metode pembantu:Java 8
Ya, ini adalah lubang kecil di API, karena itu agak tidak nyaman untuk mengubah
Optional<T>
panjang nol atau satuStream<T>
. Anda bisa melakukan ini:Memiliki operator ternary di dalamnya
flatMap
agak rumit, jadi mungkin lebih baik untuk menulis fungsi pembantu kecil untuk melakukan ini:Di sini, saya sudah sebaris panggilan untuk
resolve()
bukannya memilikimap()
operasi yang terpisah , tetapi ini adalah masalah selera.sumber
static <T> Stream<T> streamopt(Optional<T> opt) { return opt.map(Stream::of).orElse(Stream.empty()); }
Optional
kelebihan untukStream#flatMap
... dengan cara itu Anda bisa menulisstream().flatMap(this::resolve)
Optional.stream()
.Saya menambahkan jawaban kedua ini berdasarkan sunting yang diajukan oleh pengguna srborlongan ke jawaban saya yang lain . Saya pikir teknik yang diusulkan menarik, tetapi itu tidak benar-benar cocok sebagai pengeditan untuk jawaban saya. Yang lain setuju dan pengeditan yang diusulkan dibatalkan. (Aku bukan salah satu pemilih.) Namun, tekniknya pantas. Akan lebih baik jika srborlongan telah memposting jawabannya sendiri. Ini belum terjadi, dan saya tidak ingin teknik itu hilang dalam kabut StackOverflow menolak sejarah edit, jadi saya memutuskan untuk menampilkannya sebagai jawaban terpisah.
Pada dasarnya tekniknya adalah menggunakan beberapa
Optional
metode dengan cara yang cerdas untuk menghindari keharusan menggunakan operator ternary (? :
) atau pernyataan if / else.Contoh inline saya akan ditulis ulang dengan cara ini:
Contoh saya yang menggunakan metode pembantu akan ditulis ulang dengan cara ini:
KOMENTAR
Mari kita bandingkan versi asli vs yang dimodifikasi secara langsung:
Yang asli adalah pendekatan langsung jika cekatan: kita mendapatkan
Optional<Other>
; jika memiliki nilai, kami mengembalikan aliran yang berisi nilai itu, dan jika tidak memiliki nilai, kami mengembalikan aliran kosong. Cukup sederhana dan mudah dijelaskan.Modifikasi cerdas dan memiliki keuntungan bahwa ia menghindari persyaratan. (Saya tahu bahwa beberapa orang tidak menyukai operator ternary. Jika disalahgunakan memang dapat membuat kode sulit untuk dipahami.) Namun, kadang-kadang hal-hal bisa terlalu pintar. Kode yang dimodifikasi juga dimulai dengan
Optional<Other>
. Kemudian ia memanggilOptional.map
yang didefinisikan sebagai berikut:The
map(Stream::of)
panggilan mengembalikan sebuahOptional<Stream<Other>>
. Jika ada nilai pada input Opsional, Opsional yang dikembalikan berisi Stream yang berisi hasil Other lainnya. Tetapi jika nilainya tidak ada, hasilnya adalah opsional kosong.Selanjutnya, panggilan untuk
orElseGet(Stream::empty)
mengembalikan nilai tipeStream<Other>
. Jika nilai inputnya ada, itu mendapatkan nilai, yang merupakan elemen tunggalStream<Other>
. Jika tidak (jika nilai input tidak ada) mengembalikan nilai yang kosongStream<Other>
. Jadi hasilnya benar, sama dengan kode kondisional asli.Dalam komentar yang membahas jawaban saya, mengenai hasil edit yang ditolak, saya telah menggambarkan teknik ini sebagai "lebih ringkas tetapi juga lebih tidak jelas". Saya mendukung ini. Butuh beberapa saat untuk mencari tahu apa yang dilakukannya, dan juga butuh waktu untuk menulis deskripsi di atas tentang apa yang dilakukannya. Kehalusan kuncinya adalah transformasi dari
Optional<Other>
keOptional<Stream<Other>>
. Setelah Anda grok ini masuk akal, tetapi itu tidak jelas bagi saya.Namun, saya akan mengakui bahwa hal-hal yang awalnya tidak jelas dapat menjadi idiomatis dari waktu ke waktu. Mungkin saja teknik ini akhirnya menjadi cara terbaik dalam praktik, setidaknya sampai
Optional.stream
ditambahkan (jika pernah ada).PEMBARUAN:
Optional.stream
telah ditambahkan ke JDK 9.sumber
Anda tidak dapat melakukannya lebih ringkas seperti yang sudah Anda lakukan.
Anda mengklaim bahwa Anda tidak mau
.filter(Optional::isPresent)
dan.map(Optional::get)
.Ini telah diatasi dengan metode yang dijelaskan oleh @StuartMarks, namun sebagai hasilnya Anda sekarang memetakannya menjadi
Optional<T>
, jadi sekarang Anda harus menggunakan.flatMap(this::streamopt)
danget()
pada akhirnya.Jadi masih terdiri dari dua pernyataan dan sekarang Anda bisa mendapatkan pengecualian dengan metode baru! Karena, bagaimana jika setiap opsi kosong? Maka
findFirst()
akan mengembalikan opsional kosong dan Andaget()
akan gagal!Jadi apa yang Anda miliki:
sebenarnya adalah cara terbaik untuk mencapai apa yang Anda inginkan, dan itu adalah Anda ingin menyimpan hasilnya sebagai
T
, bukan sebagaiOptional<T>
.Saya mengambil kebebasan menciptakan
CustomOptional<T>
kelas yang membungkusOptional<T>
dan menyediakan metode tambahanflatStream()
,. Perhatikan bahwa Anda tidak dapat memperluasOptional<T>
:Anda akan melihat bahwa saya menambahkan
flatStream()
, seperti di sini:Digunakan sebagai:
Anda masih perlu mengembalikan sebuah di
Stream<T>
sini, karena Anda tidak dapat kembaliT
, karena jika!optional.isPresent()
, makaT == null
jika Anda menyatakannya seperti itu, tetapi kemudian Anda.flatMap(CustomOptional::flatStream)
akan berusaha untuk menambahkannull
ke aliran dan itu tidak mungkin.Sebagai contoh:
Digunakan sebagai:
Sekarang akan membuang
NullPointerException
di dalam operasi aliran.Kesimpulan
Metode yang Anda gunakan, sebenarnya adalah metode terbaik.
sumber
Versi yang sedikit lebih pendek menggunakan
reduce
:Anda juga bisa memindahkan fungsi pengurangan ke metode utilitas statis dan kemudian menjadi:
sumber
Karena jawaban saya sebelumnya tampaknya tidak terlalu populer, saya akan mencoba ini lagi.
Jawaban singkat:
Anda sebagian besar berada di jalur yang benar. Kode terpendek untuk mendapatkan hasil yang diinginkan yang dapat saya buat adalah ini:
Ini akan sesuai dengan semua kebutuhan Anda:
Optional<Result>
this::resolve
malas sesuai kebutuhanthis::resolve
tidak akan dipanggil setelah hasil non-kosong pertamaOptional<Result>
Jawaban yang lebih panjang
Satu-satunya modifikasi dibandingkan dengan versi awal OP adalah bahwa saya menghapus
.map(Optional::get)
sebelum panggilan.findFirst()
dan ditambahkan.flatMap(o -> o)
sebagai panggilan terakhir dalam rantai.Ini memiliki efek yang bagus untuk menyingkirkan double-Opsional, setiap kali streaming menemukan hasil yang sebenarnya.
Anda tidak bisa benar-benar lebih pendek dari ini di Jawa.
Potongan kode alternatif menggunakan
for
teknik loop yang lebih konvensional adalah sekitar jumlah baris kode yang sama dan memiliki urutan dan jumlah operasi yang kurang lebih sama dan perlu Anda lakukan:this.resolve
,Optional.isPresent
Hanya untuk membuktikan bahwa solusi saya berfungsi seperti yang diiklankan, saya menulis sebuah program uji kecil:
(Ini memang memiliki beberapa baris tambahan untuk debugging dan memverifikasi bahwa hanya banyak panggilan untuk diselesaikan sesuai kebutuhan ...)
Menjalankan ini pada baris perintah, saya mendapat hasil berikut:
sumber
Jika Anda tidak keberatan menggunakan perpustakaan pihak ketiga, Anda dapat menggunakan Javaslang . Ini seperti Scala, tetapi diterapkan di Jawa.
Muncul dengan perpustakaan koleksi lengkap abadi yang sangat mirip dengan yang dikenal dari Scala. Koleksi ini menggantikan koleksi Java dan Java 8's Stream. Ini juga memiliki implementasi Opsi sendiri.
Berikut adalah solusi untuk contoh pertanyaan awal:
Penafian: Saya pencipta Javaslang.
sumber
Terlambat ke pesta, tapi bagaimana
Anda dapat menyingkirkan get terakhir () jika Anda membuat metode util untuk mengonversi opsional untuk streaming secara manual:
Jika Anda langsung kembali aliran dari fungsi tekad Anda, Anda menyimpan satu baris lagi.
sumber
Saya ingin mempromosikan metode pabrik untuk membuat helper untuk API fungsional:
Metode pabrik:
Pemikiran:
Seperti dengan referensi metode secara umum, dibandingkan dengan ekspresi lambda, Anda tidak dapat secara tidak sengaja menangkap variabel dari ruang lingkup yang dapat diakses, seperti:
t -> streamopt(resolve(o))
Ini komposable, misalnya Anda dapat memanggil
Function::andThen
hasil metode pabrik:streamopt(this::resolve).andThen(...)
Sedangkan dalam kasus lambda, Anda harus melemparkannya terlebih dahulu:
((Function<T, Stream<R>>) t -> streamopt(resolve(t))).andThen(...)
sumber
Null didukung oleh Stream yang disediakan My library AbacusUtil . Ini kode:
sumber
Jika Anda terjebak dengan Java 8 tetapi memiliki akses ke Guava 21.0 atau lebih baru, Anda dapat menggunakan
Streams.stream
untuk mengonversi opsi menjadi aliran.Demikian diberikan
kamu bisa menulis
sumber
Bagaimana dengan itu?
https://stackoverflow.com/a/58281000/3477539
sumber
return list.stream().filter(Optional::isPresent).map(Optional::get).collect(Collectors.toList()))
, sama seperti pertanyaan (dan jawaban Anda yang ditautkan) memiliki ....get()
tanpaisPresent()
, maka Anda mendapatkan peringatan di IntelliJKemungkinan besar Anda salah melakukannya.
Java 8 Opsional tidak dimaksudkan untuk digunakan dengan cara ini. Biasanya hanya dicadangkan untuk operasi aliran terminal yang mungkin atau mungkin tidak mengembalikan nilai, seperti menemukan misalnya.
Dalam kasus Anda, mungkin lebih baik untuk pertama-tama mencoba menemukan cara yang murah untuk memfilter item-item yang dapat diselesaikan dan kemudian mendapatkan item pertama sebagai opsional dan menyelesaikannya sebagai operasi terakhir. Lebih baik lagi - daripada memfilter, cari item yang dapat diatasi pertama dan atasi.
Aturan praktisnya adalah Anda harus berusaha mengurangi jumlah item dalam aliran sebelum Anda mengubahnya menjadi sesuatu yang lain. YMMV tentu saja.
sumber
Optional<Result> searchFor(Term t)
. Itu tampaknya sesuai dengan niat Opsional. Selain itu, stream () harus dievaluasi dengan malas, jadi tidak ada istilah penyelesaian pekerjaan tambahan yang melewati yang cocok harus ada.