Java 8 akışları ve RxJava gözlemlenebilirleri arasındaki fark


144

Java 8 akışları RxJava gözlemlenebilirlerine benzer mi?

Java 8 akışı tanımı:

Yeni java.util.streampaketteki sınıflar , öğelerin akışları üzerinde işlevsel tarzdaki işlemleri desteklemek için bir Akış API'sı sağlar.


8
FYI JDK 9'da daha fazla RxJava benzeri sınıf tanıtmak için öneriler var. Jsr166-concurrency.10961.n7.nabble.com/…
John Vint

@JohnVint Bu teklifin durumu nedir. Aslında uçacak mı?
IgorGanapolsky

2
@IgorGanapolsky Oh evet, kesinlikle jdk9'a dönüşecek gibi görünüyor. cr.openjdk.java.net/~martin/webrevs/openjdk9/… . RxJava için Flow github.com/akarnokd/RxJavaUtilConcurrentFlow için bir bağlantı noktası bile vardır .
John Vint

Bunun gerçekten eski bir soru olduğunu biliyorum, ancak kısa süre önce konuyu kavrayan ve Java9: youtube.com/watch?v=kfSSKM9y_0E'ye güncellenen Venkat Subramaniam'ın bu büyük konuşmasına katıldım . RxJava delving insanlar için ilginç olabilir.
Pedro

Yanıtlar:


152

TL; DR : Tüm dizi / akış işleme kütüphaneleri boru hattı inşası için çok benzer bir API sunmaktadır. Farklılıklar, çoklu iş parçacığı oluşturma ve boru hatlarının bileşimi için API'dadır.

RxJava, Stream'den oldukça farklıdır. Tüm JDK şeyleri arasında, rx.Observable'a en yakın olan belki de java.util.stream.Collector Stream + CompletableFuture combo'dur (ekstra monad katmanıyla uğraşmanın bir maliyeti vardır, yani Stream<CompletableFuture<T>>ve arasında dönüştürme işlemek zorunda kalır CompletableFuture<Stream<T>>).

Gözlemlenebilir ve Akış arasında önemli farklılıklar vardır:

  • Akışlar çekme tabanlıdır, Gözlemlenebilirler itme tabanlıdır. Bu çok soyut gelebilir, ancak çok somut olan önemli sonuçları vardır.
  • Akış yalnızca bir kez kullanılabilir, Gözlemlenebilir birçok kez abone olabilir
  • Stream#parallel()bölümlere sekansı böler, Observable#subscribeOn()ve Observable#observeOn()yok; Stream#parallel()Observable ile davranışı taklit etmek zor , bir zamanlar .parallel()yöntemi vardı, ancak bu yöntem o kadar karışıklığa neden oldu ki, .parallel()destek github, RxJavaParallel üzerinde ayrı bir depoya taşındı. Daha fazla ayrıntı başka bir cevapta .
  • Stream#parallel()isteğe bağlı Zamanlayıcı kabul eden RxJava yöntemlerinin çoğundan farklı olarak, bir iş parçacığı havuzu belirtilmesine izin vermez. JVM'deki tüm akış örnekleri aynı çatal birleştirme havuzunu kullandığından, ekleme işlemi .parallel()programınızın başka bir modülündeki davranışı yanlışlıkla etkileyebilir
  • Akışları gibi zamanla ilgili işlemleri eksiktir Observable#interval(), Observable#window()ve diğerleri; bunun nedeni çoğunlukla Akımların çekmeye dayalı olması ve akış yukarı bir sonraki öğenin akış aşağısında ne zaman yayılacağı konusunda hiçbir kontrolünün olmamasıdır.
  • Akışlar, RxJava ile karşılaştırıldığında sınırlı işlem kümesi sunar. Örneğin, akışlar kesme işlemlerinden yoksun ( takeWhile(), takeUntil()); geçici çözüm Stream#anyMatch()kullanımı sınırlıdır: terminal işlemidir, bu nedenle akış başına bir defadan fazla kullanamazsınız
  • JDK 8'den itibaren, bazen oldukça yararlı olan Stream # zip işlemi yok
  • Akışları kendiniz oluşturmak zordur, Gözlemlenebilir birçok yolla inşa edilebilir EDIT: Yorumlarda belirtildiği gibi, Akış oluşturmanın yolları vardır. Bununla birlikte, terminal olmayan kısa devre olmadığından, örneğin dosyadaki satır akışını kolayca oluşturamazsınız (JDK, Dosya # satırlarını ve BufferedReader # satırlarını kutudan çıkarır ve diğer benzer senaryolar Akış oluşturularak yönetilebilir Iterator'dan).
  • Gözlemlenebilir teklifler kaynak yönetimi tesisi ( Observable#using()); IO akışını veya muteksini bununla sarabilir ve kullanıcının kaynağı serbest bırakmayı unutmayacağından emin olabilirsiniz - abonelik sonlandırmasında otomatik olarak atılır; Stream'in onClose(Runnable)yöntemi var, ancak manuel olarak veya kaynaklarla deneme yoluyla çağırmanız gerekiyor. Örneğin. Dosyalar # hatları () akılda tutmak gerekir gerekir deneyin-ile-kaynaklar blokta içine alınması.
  • Gözlemlenebilirler tümüyle senkronize edilir (Aslında Akımlar için aynı olup olmadığını kontrol etmedim). Bu, temel işlemlerin iş parçacığı açısından güvenli olup olmadığını düşünmekten sizi kurtarır (bir hata olmadığı sürece cevap her zaman 'evet' dir), ancak kodunuzun gerekip gerekmediğine bakılmaksızın eşzamanlılıkla ilgili ek yük orada olacaktır.

Yuvarlama: RxJava, Akışlardan önemli ölçüde farklıdır. Gerçek RxJava alternatifleri ReactiveStreams'in diğer uygulamalarıdır , örneğin Akka'nın ilgili kısmı.

Güncelleme . Varsayılan olmayan çatal birleştirme havuzunu kullanma hilesi var Stream#parallel, bkz . Java 8 paralel akışında özel iş parçacığı havuzu

Güncelleme . Yukarıdakilerin hepsi RxJava 1.x deneyimine dayanmaktadır. Şimdi RxJava 2.x geldiğine göre , bu cevap güncel olmayabilir.


2
Akışları oluşturmak neden zor? Bu makaleye göre, kolay görünüyor: oracle.com/technetwork/articles/java/…
IgorGanapolsky

2
'Stream' yöntemine sahip çok sayıda sınıf vardır: koleksiyonlar, giriş akışları, dizin dosyaları, vb. Ama özel bir döngüden bir akış oluşturmak istiyorsanız - örneğin, veritabanı imleci üzerinden yineleme? Şimdiye kadar bulduğum en iyi yol bir Yineleyici oluşturmak, Spliterator ile sarmak ve son olarak StreamSupport # 'u Spliterator'dan çağırmaktır. Basit bir kasa IMHO için çok fazla tutkal. Stream.iterate de var ama sonsuz akış üretiyor. Bu durumda yayını kesmenin tek yolu Akış # anyMatch'tir, ancak bu bir terminal işlemidir, bu nedenle akış üreticisini ve tüketicisini
ayıramazsınız

2
RxJava'da Observable.fromCallable, Observable.create vb. Vardır. Veya güvenle Sonsuz Gözlemlenebilir üretebilir, sonra '.takeWhile (koşul)' diyebilirsiniz ve bu sırayı tüketicilere göndermede sorun yoktur
Kirill Gamazkov

1
Akarsuları kendiniz oluşturmak zor değildir. Akıştaki bir sonraki öğeyi sağlamanız için yalnızca basit bir yöntem olan Stream.generate()kendi Supplier<U>uygulamanızı arayabilir ve iletebilirsiniz . Başka yöntemler de var. Kolay bir sekans oluşturmak için Streamkullanmak, önceki değerlerine bağlıdır interate()yöntemi, her Collectionbir yer alır stream()yöntem ve Stream.of()bir inşa Streambir varargs veya dizi den. Son olarak StreamSupport, ayırıcılar kullanarak daha gelişmiş akış oluşturma veya akış ilkel türleri için destek vardır.
jbx

Msgstr "Akışlar kesme işlemlerinden yoksun ( takeWhile(), takeUntil());" - JDK9 bunlara inanıyorum, takeWhile () ve dropWhile ()
Abdul

50

Java 8 Stream ve RxJava oldukça benzer görünüyor. Benzeri operatörlere (filtre, harita, flatMap ...) sahiptirler, ancak aynı kullanım için üretilmezler.

RxJava'yı kullanarak eşzamansız görevleri gerçekleştirebilirsiniz.

Java 8 akışı ile koleksiyonunuzun öğelerini gezeceksiniz.

RxJava'da (bir koleksiyonun çapraz öğeleri) hemen hemen aynı şeyi yapabilirsiniz, ancak RxJava eşzamanlı göreve odaklandığından, ..., senkronizasyon, mandal, ... kullanır. Yani RxJava kullanan aynı görev daha yavaş olabilir Java 8 akışı ile.

RxJava ile karşılaştırılabilir CompletableFuture, ancak bu yalnızca bir değerden fazlasını hesaplayabilir.


12
Akış geçişi ile ilgili ifadenin yalnızca paralel olmayan bir akış için doğru olduğunu belirtmek gerekir. parallelStreambasit çapraz geçişler / haritalar / filtreleme vb benzer senkronizasyonunu destekler ..
John Vint

2
"Yani RxJava kullanarak aynı görev Java 8 akışı ile daha yavaş olabilir sanmıyorum." evrensel olarak geçerli, büyük ölçüde eldeki göreve bağlı.
daschl

1
RxJava kullanarak aynı görev Java 8 akışı ile daha yavaş olabilir dedi sevindim . Bu, birçok RxJava kullanıcısının farkında olmadığı çok önemli bir ayrımdır.
IgorGanapolsky

RxJava varsayılan olarak eşzamanlıdır. Daha yavaş olabileceğini bildiren herhangi bir kıstasınız var mı?
Marcin Koziński

6
@ marcin-koziński bu kriteri kontrol edebilirsiniz: twitter.com/akarnokd/status/752465265091309568
dwursteisen

37

Birkaç teknik ve kavramsal fark vardır, örneğin, Java 8 akışları tek kullanımlık, çekme tabanlı, eşzamanlı değer dizileriyken, RxJava Gözlemlenebilirler yeniden gözlenebilir, uyarlamalı itme-çekme tabanlı, potansiyel olarak eşzamansız değer dizileridir. RxJava, Java 6+ 'yı hedefler ve Android'de de çalışır.


4
RxJava'yı içeren tipik kod, yalnızca Java 8'den itibaren kullanılabilen lambdaları yoğun olarak kullanır. Java 6 ile Rx kullanabilirsiniz, ancak kod gürültülü olacak
Kirill Gamazkov

1
Benzer bir ayrım, Rx Gözlemlenebilirliklerinin abonelikten ayrılana kadar süresiz olarak hayatta kalabilmeleridir. Java 8 akışları varsayılan olarak işlemlerle sonlandırılır.
IgorGanapolsky

2
@KirillGamazkov, Java 6'yı hedeflerken kodunuzu daha güzel hale getirmek için retrolambda'yı kullanabilirsiniz .
Marcin Koziński

Kotlin güçlendirme daha seksi görünüyor
Kirill Gamazkov

30

Java 8 Akışları çekme tabanlıdır. Her öğeyi tüketen bir Java 8 akışı üzerinden yineleme yaparsınız. Ve sonsuz bir akış olabilir.

RXJava Observablevarsayılan olarak itme tabanlıdır. Bir Gözlemlenebilir öğeye abone olursunuz ve bir sonraki öğe geldiğinde ( onNext) veya akış tamamlandığında ( onCompleted) veya bir hata oluştuğunda ( onError) bildirim alırsınız . İle Çünkü Observablealdığınız onNext, onCompleted, onErrorolaylar, farklı birleştirme gibi bazı güçlü işlevler yapabilir Observable(yenisiyle s zip, merge,concat ). Yapabileceğiniz diğer şeyler önbellekleme, daraltma, ... Ve farklı dillerde aşağı yukarı aynı API'yı kullanıyor (RxJava, C #'da RX, RxJS, ...)

Varsayılan olarak RxJava tek iş parçacıklıdır. Zamanlayıcıları kullanmaya başlamadığınız sürece her şey aynı iş parçacığında gerçekleşir.


Stream'de her biri var, bu onNext'ten hemen hemen aynı
paul

Aslında, Akışlar genellikle terminaldir. "Bir akış boru hattını kapatan işlemlere terminal işlemleri denir. Bunlar, Liste, Tamsayı veya hatta boşluk (Akış olmayan herhangi bir tür) gibi bir boru hattından sonuç üretir." ~ oracle.com/technetwork/articles/java/…
IgorGanapolsky

26

Mevcut cevaplar kapsamlı ve doğrudur, ancak yeni başlayanlar için açık bir örnek bulunmamaktadır. "İtme / çekme tabanlı" ve "yeniden gözlemlenebilir" gibi terimlerin arkasına bir somut koymama izin verin. Not : Terimden nefret ediyorum Observable(cennet uğruna bir akış), bu yüzden J8 ve RX akışlarına atıfta bulunacağım.

Tamsayıların bir listesini düşünün,

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

J8 Stream, koleksiyonu değiştirmek için bir yardımcı programdır. Örneğin, rakamlar bile şu şekilde çıkarılabilir:

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

Bu temelde Python'un harita, filtre, azaltma , Java'ya çok güzel (ve gecikmiş) bir katkıdır. Peki ya rakamlar vaktinden önce toplanmadıysa - ya uygulama çalışırken rakamlar akıyor olsaydı - çiftleri gerçek zamanlı olarak filtreleyebilirdik.

Uygulama çalışırken ayrı bir iş parçacığı işleminin rasgele zamanlarda tamsayılar çıkardığını düşünün ( ---zamanı gösterir)

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

RX yılında evenedebilirsiniz tepki her yeni rakam ve gerçek zamanlı olarak filtre uygulamak

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

Giriş ve çıkış listelerini kaydetmeye gerek yoktur. Bir çıktı listesi istiyorsanız , akıcı olmayan bir sorun da yok. Aslında, her şey bir dere.

evens_stored = even.collect()  

Bu nedenle "vatansız" ve "işlevsel" gibi terimler RX ile daha fazla ilişkilidir


Ama 5 bile değil… Ve bu J8 Stream senkronize iken, Rx Stream senkronize değil mi?
Franklin Yu

1
@FranklinYu teşekkürler 5 yazım hatasını düzelttim. Eşzamanlı vs asenkorlar açısından daha az düşünmek, doğru olsa da, daha çok zorunlu ve işlevsel olmak açısından. J8'de önce tüm öğelerinizi toplar, ardından filtreyi ikinci olarak uygularsınız. RX'te filtre işlevini verilerden bağımsız olarak tanımlar ve ardından eşit bir kaynakla (canlı akış veya java koleksiyonu) ilişkilendirirsiniz ... tamamen farklı bir programlama modelidir
Adam Hughes

Buna çok şaşırdım. Java akışlarının veri akışından yapılabileceğinden eminim. Bunun tam tersini düşündüren nedir?
Vic Seedoubleyew

4

RxJava aynı zamanda reaktif akışlar girişimi ile yakından ilgilidir ve kendisini reaktif akışlar API'sinin basit bir uygulaması olarak değerlendirir (örneğin Akka akışları uygulamasına kıyasla ). Temel fark, reaktif akışların geri basıncı kaldırabilecek şekilde tasarlanmasıdır, ancak reaktif akışlar sayfasına bir göz atarsanız, fikir elde edersiniz. Hedeflerini oldukça iyi açıklarlar ve akışlar da reaktif manifesto ile yakından ilişkilidir .

Java 8 akışları, Scala Stream veya Clojure tembel seq'e oldukça benzer, sınırsız bir koleksiyonun uygulanmasıdır .


3

Java 8 Streams, çok çekirdekli mimarilerden faydalanırken gerçekten büyük koleksiyonların verimli bir şekilde işlenmesini sağlar. Buna karşılık, RxJava varsayılan olarak tek programlıdır (Zamanlayıcılar olmadan). Bu nedenle, bu mantığı kendiniz kodlamadığınız sürece RxJava çok çekirdekli makinelerden yararlanmayacaktır.


4
.Parallel () öğesini çağırmazsanız akış varsayılan olarak tek iş parçacıklıdır. Ayrıca, Rx eşzamanlılık üzerinde daha fazla kontrol sağlar.
Kirill Gamazkov

@KirillGamazkov Kotlin Coroutines Flow (Java8 Akışlarına dayalı) artık yapılandırılmış eşzamanlılığı destekliyor: kotlinlang.org/docs/reference/coroutines/flow.html#flows
IgorGanapolsky

Doğru, ama Flow ve yapılandırılmış eşzamanlılık hakkında hiçbir şey söylemedim. İki noktam şunlardı: 1) Açıkça değiştirmediğiniz sürece hem Stream hem de Rx tek iş parçacıklı; 2) Rx, yalnızca "bir şekilde paralel yap"
demenize

Gerçekten "ne için iplik havuzu gerekiyor" sorusunun anlamını alamıyorum. Söylediğiniz gibi, "gerçekten büyük koleksiyonların verimli bir şekilde işlenmesini sağlamak". Ya da belki görev IO bağlı bir parçası ayrı iş parçacığı havuzu üzerinde çalışmasını istiyorum. Sorunuzun arkasındaki niyeti anladığımı sanmıyorum. Tekrar deneyin?
Kirill Gamazkov

1
Zamanlayıcılar sınıfındaki statik yöntemler, önceden tanımlanmış iş parçacığı havuzları almanın yanı sıra Executor'tan bir tane oluşturmanıza da olanak tanır. Bkz reactivex.io/RxJava/2.x/javadoc/io/reactivex/schedulers/...
Kirill Gamazkov
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.