Differenza tra flussi Java 8 e osservabili RxJava


144

I flussi Java 8 sono simili agli osservabili RxJava?

Definizione stream Java 8:

Le classi nel nuovo java.util.streampacchetto forniscono un'API Stream per supportare operazioni in stile funzionale su flussi di elementi.


8
Cordiali saluti, ci sono proposte per introdurre più classi simili a RxJava in JDK 9. jsr166-concurrency.10961.n7.nabble.com/…
John Vint,

@JohnVint Qual è lo stato di questa proposta. Prenderà effettivamente il volo?
IgorGanapolsky

2
@IgorGanapolsky Oh sì, sembra proprio che diventerà jdk9. cr.openjdk.java.net/~martin/webrevs/openjdk9/… . Esiste persino una porta per RxJava to Flow github.com/akarnokd/RxJavaUtilConcurrentFlow .
John Vint,

So che questa è una domanda davvero vecchia, ma di recente ho partecipato a questo grande discorso di Venkat Subramaniam che ha un'intuizione approfondita sull'argomento e viene aggiornato a Java9: youtube.com/watch?v=kfSSKM9y_0E . Potrebbe essere interessante per le persone che approfondiscono RxJava.
Pedro

Risposte:


152

TL; DR : tutte le librerie di elaborazione sequenza / flusso offrono API molto simili per la creazione di pipeline. Le differenze sono nelle API per la gestione del multi-threading e della composizione delle condutture.

RxJava è abbastanza diverso da Stream. Di tutte le cose JDK, il più vicino a rx.Observable è forse java.util.stream.Collector Stream + CompletableFuture combo (che ha un costo per gestire un livello extra di monade, cioè dover gestire la conversione tra Stream<CompletableFuture<T>>e CompletableFuture<Stream<T>>).

Esistono differenze significative tra Observable e Stream:

  • Gli stream sono basati su pull, gli osservabili sono basati su push. Questo può sembrare troppo astratto, ma ha conseguenze significative che sono molto concrete.
  • Il flusso può essere utilizzato solo una volta, Osservabile può essere sottoscritto più volte
  • Stream#parallel()divide la sequenza in partizioni Observable#subscribeOn()e Observable#observeOn()non; è difficile emulare il Stream#parallel()comportamento con Observable, una volta aveva il .parallel()metodo ma questo metodo causava tanta confusione che il .parallel()supporto è stato spostato in un repository separato su github, RxJavaParallel. Maggiori dettagli sono in un'altra risposta .
  • Stream#parallel()non consente di specificare un pool di thread da utilizzare, a differenza della maggior parte dei metodi RxJava che accettano l'Utilità di pianificazione opzionale. Poiché tutte le istanze di streaming in una JVM utilizzano lo stesso pool fork-join, l'aggiunta .parallel()può influire accidentalmente sul comportamento in un altro modulo del programma
  • Flussi mancano le operazioni relative al tempo, come Observable#interval(), Observable#window()e molti altri; ciò è principalmente dovuto al fatto che gli stream sono basati su pull e upstream non ha alcun controllo su quando emettere l'elemento successivo a valle
  • Gli stream offrono un insieme limitato di operazioni rispetto a RxJava. Ad esempio, agli stream mancano le operazioni di interruzione ( takeWhile(), takeUntil()); la soluzione alternativa Stream#anyMatch()è limitata: è un'operazione terminale, quindi non è possibile utilizzarla più di una volta per flusso
  • A partire da JDK 8, non esiste alcuna operazione Stream # zip, che a volte è abbastanza utile
  • Gli stream sono difficili da costruire da soli, Observable può essere costruito in molti modi EDIT: come notato nei commenti, ci sono modi per costruire Stream. Tuttavia, poiché non esiste un cortocircuito non terminale, ad esempio non è possibile generare facilmente Stream di linee nel file (JDK fornisce comunque File # linee e BufferedReader # linee pronte all'uso e altri scenari simili possono essere gestiti costruendo Stream da Iterator).
  • Strumento di gestione delle risorse delle offerte osservabili ( Observable#using()); puoi avvolgere IO Stream o Mutex con esso ed essere sicuro che l'utente non dimenticherà di liberare la risorsa: verrà eliminata automaticamente al termine dell'abbonamento; Lo streaming ha un onClose(Runnable)metodo, ma devi chiamarlo manualmente o tramite prova con risorse. Per esempio. devi tenere presente che File # righe () deve essere racchiuso nel blocco try-with-resources.
  • Gli osservabili sono completamente sincronizzati (in realtà non ho verificato se lo stesso è vero per gli stream). Questo ti evita di pensare se le operazioni di base sono thread-safe (la risposta è sempre "sì", a meno che non sia presente un bug), ma l'overhead relativo alla concorrenza sarà presente, indipendentemente dal fatto che il codice ne abbia bisogno o meno.

Riassunto: RxJava differisce significativamente dagli stream. Le alternative reali a RxJava sono altre implementazioni di ReactiveStreams , ad esempio la parte rilevante di Akka.

Aggiornamento . Esistono dei trucchi per utilizzare il pool fork-join non predefinito Stream#parallel, consultare Pool di thread personalizzati nel flusso parallelo Java 8

Aggiornamento . Tutto quanto sopra si basa sull'esperienza con RxJava 1.x. Ora che RxJava 2.x è qui , questa risposta potrebbe non essere aggiornata.


2
Perché gli stream sono difficili da costruire? Secondo questo articolo, sembra facile: oracle.com/technetwork/articles/java/…
IgorGanapolsky

2
Esistono un certo numero di classi che hanno il metodo "stream": raccolte, flussi di input, file di directory, ecc. Ma cosa succede se si desidera creare un flusso da un ciclo personalizzato, ad esempio iterando sul cursore del database? Il modo migliore che ho trovato finora è creare un Iterator, avvolgerlo con Spliterator e infine invocare StreamSupport # fromSpliterator. Troppa colla per un semplice caso IMHO. C'è anche Stream.iterate ma produce un flusso infinito. L'unico modo per eliminare l'urlo in quel caso è Stream # anyMatch, ma è un'operazione terminale, quindi non è possibile separare produttore e consumatore dello stream
Kirill Gamazkov

2
RxJava ha Observable.fromCallable, Observable.create e così via. Oppure puoi produrre in modo sicuro Osservabile infinito, quindi dì ".takeWhile (condition)" e stai bene spedendo questa sequenza ai consumatori
Kirill Gamazkov

1
I flussi non sono difficili da costruire da soli. Puoi semplicemente chiamare Stream.generate()e passare la tua Supplier<U>implementazione, solo un metodo semplice da cui fornire l'elemento successivo nello stream. Ci sono molti altri metodi. Per costruire facilmente una sequenza Streamche dipende da valori precedenti puoi usare il interate()metodo, ognuno Collectionha un stream()metodo e Stream.of()costruisce un Streamda un varargs o un array. Finalmente StreamSupportha il supporto per la creazione di stream più avanzata usando spliterator o per tipi primitivi di stream.
jbx,

"I flussi mancano di operazioni di interruzione ( takeWhile(), takeUntil());" - JDK9 ha questi, credo, in takeWhile () e dropWhile ()
Abdul

50

Java 8 Stream e RxJava sembrano abbastanza simili. Hanno operatori simili (filtro, mappa, flatMap ...) ma non sono costruiti per lo stesso utilizzo.

È possibile eseguire attività asincrone utilizzando RxJava.

Con Java 8 stream, attraverserai oggetti della tua collezione.

Puoi fare praticamente la stessa cosa in RxJava (attraversare gli elementi di una raccolta) ma, poiché RxJava è focalizzato su attività simultanee, ... usa sincronizzazione, latch, ... Quindi la stessa attività usando RxJava potrebbe essere più lenta di con flusso Java 8.

RxJava può essere paragonato a CompletableFuture, ma può essere in grado di calcolare più di un solo valore.


12
Vale la pena notare che l'affermazione sull'attraversamento del flusso è vera solo per un flusso non parallelo. parallelStreamsupporta una sincronizzazione simile di semplici attraversamenti / mappe / filtri ecc.
John Vint,

2
Non penso "Quindi lo stesso compito usando RxJava potrebbe essere più lento rispetto al flusso Java 8". è vero universalmente, dipende fortemente dal compito da svolgere.
Daschl,

1
Sono contento che tu abbia detto che lo stesso compito usando RxJava potrebbe essere più lento rispetto al flusso Java 8 . Questa è una distinzione molto importante di cui molti utenti RxJava non sono a conoscenza.
IgorGanapolsky

RxJava è sincrono per impostazione predefinita. Hai dei parametri di riferimento a supporto della tua affermazione che potrebbe essere più lento?
Marcin Koziński,

6
@ marcin-koziński puoi controllare questo benchmark: twitter.com/akarnokd/status/752465265091309568
dwursteisen

37

Esistono alcune differenze tecniche e concettuali, ad esempio i flussi Java 8 sono sequenze di valori sincrone monouso, basate su pull, mentre le osservabili RxJava sono ri-osservabili, sequenze di valori adattivamente basate su push-pull, potenzialmente asincrone di valori. RxJava è rivolto a Java 6+ e funziona anche su Android.


4
Il codice tipico che coinvolge RxJava fa un uso pesante di lambda che sono disponibili solo da Java 8 in poi. Quindi puoi usare Rx con Java 6, ma il codice sarà rumoroso
Kirill Gamazkov

1
Una distinzione simile è che Rx Observables può rimanere in vita indefinitamente fino a quando non è iscritto. I flussi Java 8 sono terminati con le operazioni per impostazione predefinita.
IgorGanapolsky

2
@KirillGamazkov puoi usare retrolambda per rendere il tuo codice più carino quando scegli come target Java 6.
Marcin Koziński,

Kotlin sembra ancora più sexy del retrofit
Kirill Gamazkov,

30

I flussi Java 8 sono basati su pull. Si scorre su un flusso Java 8 che consuma ogni elemento. E potrebbe essere un flusso infinito.

RXJava Observableè basato su push predefinito. Ti iscrivi a un osservabile e riceverai una notifica quando arriva l'elemento successivo ( onNext) o quando lo stream è completato ( onCompleted) o quando si è verificato un errore ( onError). Perché con Observablesi riceve onNext, onCompleted, onErroreventi, si possono fare alcune funzioni potenti come la combinazione di diversi Observables ad una nuova ( zip, merge, concat). Altre cose che potresti fare sono la memorizzazione nella cache, la limitazione, ... E utilizza più o meno la stessa API in diverse lingue (RxJava, RX in C #, RxJS, ...)

Per impostazione predefinita, RxJava è a thread singolo. A meno che non inizi a utilizzare gli Scheduler, tutto accadrà sullo stesso thread.


in Stream hai per ogni, che è praticamente lo stesso che in Next
paul

In realtà, gli stream sono in genere terminali. "Le operazioni che chiudono una pipeline dello stream sono chiamate operazioni terminal. Producono un risultato da una pipeline come un Elenco, un Numero intero o addirittura un vuoto (qualsiasi tipo non Stream)." ~ oracle.com/technetwork/articles/java/…
IgorGanapolsky

26

Le risposte esistenti sono complete e corrette, ma manca un chiaro esempio per i principianti. Consentitemi di inserire alcuni termini concreti come "push / pull-based" e "ri-osservabile". Nota : odio il termine Observable(è un flusso per l'amor del cielo), quindi farò semplicemente riferimento ai flussi J8 vs RX.

Considera un elenco di numeri interi,

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

Un flusso J8 è un'utilità per modificare la raccolta. Ad esempio, anche le cifre possono essere estratte come,

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

Questa è fondamentalmente la mappa, il filtro, la riduzione di Python , un'aggiunta molto bella (e molto attesa) a Java. E se le cifre non fossero state raccolte in anticipo - e se le cifre fossero state trasmesse in streaming mentre l'app era in esecuzione - potremmo filtrare le cifre pari in tempo reale.

Immagina che un processo di thread separato stia emettendo numeri interi in momenti casuali mentre l'app è in esecuzione ( ---indica il tempo)

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

In RX, evenpuò reagire ad ogni nuova cifra e applicare il filtro in tempo reale

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

Non è necessario memorizzare elenchi di input e output. Se si desidera un elenco di output, nessun problema è anche streaming. In effetti, tutto è un flusso.

evens_stored = even.collect()  

Questo è il motivo per cui termini come "senza stato" e "funzionale" sono più associati a RX


Ma 5 non è nemmeno ... E sembra che J8 Stream sia sincrono, mentre Rx Stream sia asincrono?
Franklin Yu,

1
@FranklinYu grazie ho corretto il 5 errore di battitura. Se pensi meno in termini di sincroni vs asincroni, anche se può essere corretto, e più in termini di imperativo vs funzionale. In J8, raccogli prima tutti i tuoi oggetti, quindi applica il secondo filtro. In RX definisci la funzione di filtro indipendentemente dai dati e poi la associ a una fonte pari (un flusso live o una raccolta java) ... è un modello di programmazione completamente diverso
Adam Hughes,

Sono molto sorpreso da questo. Sono abbastanza sicuro che i flussi Java possano essere fatti di streaming di dati. Cosa ti fa pensare il contrario?
Vic Seedoubleyew,

4

RxJava è anche strettamente correlato all'iniziativa dei flussi reattivi e si considera una semplice implementazione dell'API dei flussi reattivi (ad esempio rispetto all'implementazione dei flussi Akka ). La differenza principale è che i flussi reattivi sono progettati per essere in grado di gestire la contropressione, ma se dai un'occhiata alla pagina dei flussi reattivi, otterrai l'idea. Descrivono abbastanza bene i loro obiettivi e anche i flussi sono strettamente correlati al manifesto reattivo .

I flussi Java 8 sono praticamente l'implementazione di una collezione illimitata, abbastanza simile al Scala Stream o al pigro seq di Clojure .


3

Java 8 Streams consente l'elaborazione di raccolte di grandi dimensioni in modo efficiente, sfruttando al contempo le architetture multicore. Al contrario, RxJava è a thread singolo per impostazione predefinita (senza Scheduler). Quindi RxJava non sfrutterà le macchine multi-core a meno che non codifichi tu stesso quella logica.


4
Anche lo stream è a thread singolo per impostazione predefinita, a meno che non invochi .parallel (). Inoltre, Rx offre un maggiore controllo sulla concorrenza.
Kirill Gamazkov,

@KirillGamazkov Kotlin Coroutines Flow (basato su Java8 Streams) ora supporta la concorrenza strutturata: kotlinlang.org/docs/reference/coroutines/flow.html#flows
IgorGanapolsky

È vero, ma non ho detto nulla su Flow e sulla concorrenza strutturata. I miei due punti erano: 1) sia Stream che Rx sono a thread singolo a meno che tu non lo cambi esplicitamente; 2) Rx ti offre un controllo accurato su quale passaggio eseguire su quale pool di thread, in contrasto con gli stream che ti consentono solo di dire "
rendilo

Non capisco davvero il punto della domanda "per cosa hai bisogno del pool di thread". Come hai detto, "per consentire l'elaborazione di raccolte di grandi dimensioni in modo efficiente". O forse voglio che parte dell'attività assegnata a IO venga eseguita su un pool di thread separato. Non credo di aver capito l'intenzione alla base della tua domanda. Riprova?
Kirill Gamazkov,

1
I metodi statici nella classe Schedulers consentono di ottenere pool di thread predefiniti e di crearne uno da Executor. Vedi reattivex.io/RxJava/2.x/javadoc/io/reactivex/schedulers/…
Kirill Gamazkov
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.