Perbedaan antara stream Java 8 dan RxJava yang dapat diamati

144

Apakah aliran Java 8 mirip dengan yang dapat diamati RxJava?

Definisi Java 8 stream:

Kelas-kelas dalam java.util.streampaket baru menyediakan Stream API untuk mendukung operasi gaya fungsional pada aliran elemen.

rahulrv
sumber
8
FYI ada proposal untuk memperkenalkan lebih banyak RxJava seperti kelas di JDK 9. jsr166-concurrency.10961.n7.nabble.com/...
John Vint
@JohnVint Apa status proposal ini. Apakah itu benar-benar akan terbang?
IgorGanapolsky
2
@IgorGanapolsky Oh ya, itu pasti akan membuatnya menjadi jdk9. cr.openjdk.java.net/~martin/webrevs/openjdk9/… . Bahkan ada port untuk RxJava untuk Mengalir github.com/akarnokd/RxJavaUtilConcurrentFlow .
John Vint
Saya tahu ini adalah pertanyaan yang sangat lama, tetapi saya baru-baru ini menghadiri pembicaraan hebat ini oleh Venkat Subramaniam yang memiliki wawasan mendalam tentang masalah ini dan diperbarui ke Java9: youtube.com/watch?v=kfSSKM9y_0E . Bisa jadi menarik bagi orang yang mempelajari RxJava.
Pedro

Jawaban:

152

TL; DR : Semua lib pemrosesan urutan / aliran menawarkan API yang sangat mirip untuk pembangunan saluran pipa. Perbedaannya ada di API untuk menangani multi-threading dan komposisi saluran pipa.

RxJava sangat berbeda dari Stream. Dari semua hal JDK, yang terdekat dengan rx.Observable mungkin java.util.stream.Collector Stream + CompletableFuture combo (yang datang dengan biaya berurusan dengan lapisan monad tambahan, yaitu harus menangani konversi antara Stream<CompletableFuture<T>>dan CompletableFuture<Stream<T>>).

Ada perbedaan yang signifikan antara Observable dan Stream:

  • Streaming berbasis tarik, Observable berbasis push. Ini mungkin terdengar terlalu abstrak, tetapi memiliki konsekuensi signifikan yang sangat konkret.
  • Streaming hanya dapat digunakan sekali, Dapat diamati dapat berlangganan berkali-kali
  • Stream#parallel()Membagi urutan menjadi partisi, Observable#subscribeOn()dan Observable#observeOn()tidak; sulit untuk meniru Stream#parallel()perilaku dengan Observable, ia pernah memiliki .parallel()metode tetapi metode ini menyebabkan banyak kebingungan sehingga .parallel()dukungan dipindahkan ke repositori terpisah di github, RxJavaParallel. Lebih detail ada di jawaban lain .
  • Stream#parallel()tidak mengizinkan untuk menentukan kumpulan utas untuk digunakan, tidak seperti kebanyakan metode RxJava menerima Penjadwal opsional. Karena semua instance stream dalam JVM menggunakan fork-join pool yang sama, menambahkan .parallel()secara tidak sengaja dapat mempengaruhi perilaku di modul lain dari program Anda
  • Streaming kurang memiliki operasi terkait waktu seperti Observable#interval(), Observable#window()dan banyak lainnya; ini sebagian besar karena Streaming berbasis tarik, dan hulu tidak memiliki kontrol kapan harus memancarkan elemen berikutnya hilir
  • Streaming menawarkan rangkaian operasi terbatas dibandingkan dengan RxJava. Misalnya Streaming kurang operasi cut-off ( takeWhile(), takeUntil()); penggunaan solusi Stream#anyMatch()terbatas: ini adalah operasi terminal, jadi Anda tidak dapat menggunakannya lebih dari sekali per aliran
  • Pada JDK 8, tidak ada operasi Stream # zip, yang kadang-kadang sangat berguna
  • Streaming sulit untuk dibangun sendiri, Dapat diamati dapat dibangun dengan banyak cara. EDIT: Seperti disebutkan dalam komentar, ada cara untuk membangun Stream. Namun, karena tidak ada hubungan arus pendek non-terminal, Anda tidak dapat misalnya dengan mudah menghasilkan Stream of lines dalam file (JDK menyediakan baris Files # dan BufferedReader # di luar kotak sekalipun, dan skenario serupa lainnya dapat dikelola dengan membangun Stream dari Iterator).
  • Penawaran yang dapat diamati adalah fasilitas manajemen sumber daya ( Observable#using()); Anda dapat membungkus aliran IO atau mutex dengannya dan memastikan bahwa pengguna tidak akan lupa untuk membebaskan sumber daya - itu akan dibuang secara otomatis pada penghentian berlangganan; Streaming memiliki onClose(Runnable)metode, tetapi Anda harus menyebutnya secara manual atau melalui coba-dengan-sumber daya. E. g. Anda harus ingat bahwa Files # lines () harus dilampirkan dalam blok try-with-resources.
  • Dapat diamati disinkronkan sepanjang jalan (saya tidak benar-benar memeriksa apakah hal yang sama berlaku untuk Streaming). Ini membuat Anda terhindar dari berpikir apakah operasi dasar aman-thread (jawabannya selalu 'ya', kecuali ada bug), tetapi overhead terkait-konkurensi akan ada di sana, tidak peduli apakah kode Anda memerlukannya atau tidak.

Round-up: RxJava berbeda dari Streaming secara signifikan. Alternatif RxJava nyata adalah implementasi lain dari ReactiveStreams , misalnya bagian yang relevan dari Akka.

Perbarui . Ada trik untuk menggunakan pool-fork-join non-default untuk Stream#parallel, lihat pool thread kustom di Java 8 stream paralel

Perbarui . Semua hal di atas didasarkan pada pengalaman dengan RxJava 1.x. Sekarang RxJava 2.x ada di sini , jawaban ini mungkin kedaluwarsa.

Kirill Gamazkov
sumber
2
Mengapa Streaming sulit dibangun? Menurut artikel ini, tampaknya mudah: oracle.com/technetwork/articles/java/…
IgorGanapolsky
2
Ada cukup banyak kelas yang memiliki metode 'stream': koleksi, input stream, file direktori, dll. Tetapi bagaimana jika Anda ingin membuat stream dari custom loop - katakanlah, iterasi di atas kursor basis data? Cara terbaik yang saya temukan sejauh ini adalah membuat Iterator, membungkusnya dengan Spliterator, dan akhirnya memohon StreamSupport # fromSpliterator. Terlalu banyak lem untuk IMHO kasus sederhana. Ada juga Stream.iterate tetapi menghasilkan aliran yang tak terbatas. Satu-satunya cara untuk menghentikan sream dalam kasus itu adalah Stream # anyMatch, tetapi ini adalah operasi terminal, sehingga Anda tidak dapat memisahkan produsen dan konsumen aliran
Kirill Gamazkov
2
RxJava memiliki Observable.fromCallable, Observable.create dan sebagainya. Atau Anda dapat dengan aman menghasilkan infable Observable, lalu mengatakan '.takeWhile (kondisi)', dan Anda setuju dengan pengiriman urutan ini ke konsumen
Kirill Gamazkov
1
Streaming tidak sulit dibangun sendiri. Anda cukup memanggil Stream.generate()dan meneruskan Supplier<U>implementasi Anda sendiri , hanya satu metode sederhana dari mana Anda memberikan item berikutnya dalam aliran. Ada banyak metode lain. Untuk dengan mudah membangun urutan Streamyang tergantung pada nilai-nilai sebelumnya Anda dapat menggunakan interate()metode ini, setiap Collectionmemiliki stream()metode dan Stream.of()membangun Streamdari varargs atau array. Akhirnya StreamSupportmemiliki dukungan untuk pembuatan aliran yang lebih maju menggunakan spliterator atau untuk aliran jenis primitif.
jbx
"Streaming kekurangan operasi cut-off ( takeWhile(), takeUntil());" - JDK9 memiliki ini, saya percaya, di takeWhile () dan dropWhile ()
Abdul
50

Java 8 Stream dan RxJava terlihat sangat mirip. Mereka memiliki operator yang mirip (filter, peta, flatMap ...) tetapi tidak dibuat untuk penggunaan yang sama.

Anda dapat melakukan tugas asynchonus menggunakan RxJava.

Dengan streaming Java 8, Anda akan melintasi item koleksi Anda.

Anda dapat melakukan hal yang hampir sama di RxJava (melintasi item koleksi) tetapi, karena RxJava difokuskan pada tugas bersamaan, ..., ia menggunakan sinkronisasi, kait, ... Jadi tugas yang sama menggunakan RxJava mungkin lebih lambat daripada dengan Java 8 stream.

RxJava dapat dibandingkan dengan CompletableFuture, tetapi itu bisa dapat menghitung lebih dari satu nilai.

dwursteisen
sumber
12
Perlu dicatat bahwa pernyataan Anda tentang stream traversal hanya berlaku untuk aliran non-paralel. parallelStreammendukung sinkronisasi serupa dari lintasan sederhana / peta / penyaringan dll.
John Vint
2
Saya tidak berpikir "Jadi tugas yang sama menggunakan RxJava mungkin lebih lambat daripada dengan aliran Java 8." berlaku universal, sangat tergantung pada tugas yang dihadapi.
daschl
1
Saya senang Anda mengatakan tugas yang sama menggunakan RxJava mungkin lebih lambat dibandingkan dengan aliran Java 8 . Ini adalah perbedaan yang sangat penting yang tidak disadari oleh banyak pengguna RxJava.
IgorGanapolsky
RxJava sinkron secara default. Apakah Anda memiliki tolok ukur untuk mendukung pernyataan Anda bahwa mungkin lebih lambat?
Marcin Koziński
6
@ marcin-koziński Anda dapat memeriksa tolok ukur ini: twitter.com/akarnokd/status/752465265091309568
dwursteisen
37

Ada beberapa perbedaan teknis dan konseptual, misalnya, aliran Java 8 adalah penggunaan tunggal, tarik berbasis, urutan nilai sinkron sedangkan RxJava Observable dapat diamati kembali, adaptif dorong-tarik berbasis, berpotensi urutan nilai asinkron. RxJava ditujukan untuk Java 6+ dan bekerja di Android juga.

akarnokd
sumber
4
Kode umum yang melibatkan RxJava banyak menggunakan lambdas yang hanya tersedia dari Java 8 pada. Jadi Anda dapat menggunakan Rx dengan Java 6, tetapi kodenya akan berisik
Kirill Gamazkov
1
Perbedaan yang serupa adalah bahwa Rx Observables dapat tetap hidup tanpa batas waktu sampai berhenti berlangganan. Java 8 stream diakhiri dengan operasi secara default.
IgorGanapolsky
2
@ KirillGamazkov Anda dapat menggunakan retrolambda untuk membuat kode Anda lebih cantik saat menargetkan Java 6.
Marcin Koziński
Kotlin bahkan terlihat lebih seksi daripada retrofit
Kirill Gamazkov
30

Java 8 Streaming berbasis tarik. Anda beralih menggunakan streaming Java 8 untuk setiap item. Dan itu bisa menjadi aliran tanpa akhir.

RXJava Observablesecara default berbasis push. Anda berlangganan Observable dan Anda akan diberitahu ketika item berikutnya tiba ( onNext), atau ketika aliran selesai ( onCompleted), atau ketika kesalahan terjadi ( onError). Karena dengan ObservableAnda menerima onNext, onCompleted, onErrorperistiwa, Anda dapat melakukan beberapa fungsi yang kuat seperti menggabungkan berbeda Observables ke yang baru ( zip, merge, concat). Hal lain yang dapat Anda lakukan adalah caching, pembatasan, ... Dan ia menggunakan API yang kurang lebih sama dalam berbagai bahasa (RxJava, RX dalam C #, RxJS, ...)

Secara default RxJava adalah utas tunggal. Kecuali Anda mulai menggunakan Penjadwal, semuanya akan terjadi pada utas yang sama.

Bart De Neuter
sumber
dalam Stream yang Anda miliki untuk masing-masing, itu hampir sama dengan onNext
paul
Sebenarnya, stream biasanya terminal. "Operasi yang menutup pipa aliran disebut operasi terminal. Mereka menghasilkan hasil dari pipa seperti Daftar, Integer, atau bahkan batal (jenis non-Stream)." ~ oracle.com/technetwork/articles/java/…
IgorGanapolsky
26

Jawaban yang ada komprehensif dan benar, tetapi contoh yang jelas untuk pemula masih kurang. Izinkan saya untuk meletakkan beberapa konkret di belakang istilah seperti "push / pull-based" dan "re-observable". Catatan : Saya benci istilah Observable(itu aliran demi Tuhan), jadi cukup merujuk ke aliran J8 vs RX.

Pertimbangkan daftar bilangan bulat,

digits = [1,2,3,4,5]

J8 Stream adalah utilitas untuk memodifikasi koleksi. Misalnya digit bahkan dapat diekstraksi sebagai,

evens = digits.stream().filter(x -> x%2).collect(Collectors.toList())

Ini pada dasarnya adalah peta Python , filter, kurangi , tambahan yang sangat bagus (dan lama tertunda) ke Java. Tetapi bagaimana jika digit tidak dikumpulkan sebelumnya - bagaimana jika digitnya mengalir saat aplikasi sedang berjalan - dapatkah kita menyaring bahkan dalam waktu nyata.

Bayangkan proses utas terpisah menghasilkan bilangan bulat secara acak saat aplikasi sedang berjalan ( ---menunjukkan waktu)

digits = 12345---6------7--8--9-10--------11--12

Di RX, evendapat bereaksi terhadap setiap digit baru dan menerapkan filter secara real-time

even = -2-4-----6---------8----10------------12

Tidak perlu menyimpan daftar input dan output. Jika Anda menginginkan daftar keluaran, tidak ada masalah yang dapat dialirkan juga. Faktanya, semuanya adalah aliran.

evens_stored = even.collect()  

Inilah sebabnya mengapa istilah seperti "stateless" dan "fungsional" lebih terkait dengan RX

Adam Hughes
sumber
Tapi 5 bahkan tidak ... Dan sepertinya J8 Stream sinkron, sedangkan Rx Stream asinkron?
Franklin Yu
1
@ FranklinYu terima kasih sudah memperbaiki kesalahan ketik 5. Jika berpikir kurang dalam hal sinkron vs sinkron, meskipun mungkin benar, dan lebih dalam hal imperatif vs fungsional. Di J8, Anda mengumpulkan semua item Anda terlebih dahulu, lalu menerapkan filter kedua. Di RX, Anda menentukan fungsi filter yang tidak bergantung pada data, dan kemudian mengaitkannya dengan sumber yang sama (aliran langsung, atau koleksi java) ... ini adalah model pemrograman yang sama sekali berbeda
Adam Hughes
Saya sangat terkejut dengan ini. Saya cukup yakin Java stream dapat dibuat dari streaming data masuk. Apa yang membuat Anda berpikir sebaliknya?
Vic Seedoubleyew
4

RxJava juga terkait erat dengan inisiatif stream reaktif dan menganggapnya sebagai implementasi sederhana dari API stream reaktif (misalnya dibandingkan dengan implementasi stream Akka ). Perbedaan utama adalah, bahwa aliran reaktif dirancang untuk dapat menangani tekanan balik, tetapi jika Anda melihat halaman aliran reaktif, Anda akan mendapatkan ide. Mereka menggambarkan tujuan mereka dengan cukup baik dan alirannya juga terkait erat dengan manifesto reaktif .

Java 8 stream cukup banyak penerapan koleksi tak terbatas, sangat mirip dengan Scala Stream atau Clojure lazy seq .

Niclas Meier
sumber
3

Java 8 Streaming memungkinkan pemrosesan koleksi yang sangat besar secara efisien, sambil meningkatkan arsitektur multicore. Sebaliknya, RxJava adalah single-threaded secara default (tanpa Penjadwal). Jadi RxJava tidak akan mengambil keuntungan dari mesin multi-core kecuali jika Anda membuat kode logika sendiri.

IgorGanapolsky
sumber
4
Stream juga single-threaded, kecuali Anda memanggil .parallel (). Juga, Rx memberikan kontrol lebih besar atas konkurensi.
Kirill Gamazkov
@KirillGamazkov Kotlin Coroutines Flow (berdasarkan Java8 Streams) sekarang mendukung konkurensi terstruktur: kotlinlang.org/docs/reference/coroutines/flow.html#flows
IgorGanapolsky
Benar, tetapi saya tidak mengatakan apa-apa tentang Flow dan konkurensi terstruktur. Dua poin saya adalah: 1) Stream dan Rx adalah single-threaded kecuali Anda secara eksplisit mengubahnya; 2) Rx memberi Anda kendali yang baik pada langkah mana yang harus dilakukan pada kumpulan utas mana, berbeda dengan Streams yang hanya memungkinkan Anda untuk mengatakan "buatlah itu menjadi paralel"
Kirill Gamazkov
Saya tidak benar-benar mendapatkan poin dari pertanyaan "untuk apa Anda memerlukan kumpulan utas". Seperti yang Anda katakan, "untuk memungkinkan pemrosesan koleksi yang sangat besar secara efisien". Atau mungkin saya ingin bagian tugas yang terikat IO dijalankan di utas terpisah. Saya tidak berpikir saya mengerti maksud di balik pertanyaan Anda. Coba lagi?
Kirill Gamazkov
1
Metode statis di kelas Penjadwal memungkinkan untuk mendapatkan kumpulan utas yang telah ditentukan serta membuatnya dari Executor. Lihat reactivex.io/RxJava/2.x/javadoc/io/reactivex/schedulers/…
Kirill Gamazkov