1. Überblick
In diesem Artikel konzentrieren wir uns auf die Verwendung von Reactive Extensions (Rx) in Java, um Datensequenzen zu erstellen und zu verwenden.
Auf einen Blick mag die API Java 8-Streams ähneln, ist jedoch in der Tat viel flexibler und fließender, was sie zu einem leistungsfähigen Programmierparadigma macht.
Wenn Sie mehr über RxJava erfahren möchten, besuchen Sie den Link:/rxjava-backpressure[this writeup].
2. Konfiguration
Um RxJava in unserem Projekt Maven zu verwenden, müssen wir unserem pom die folgende Abhängigkeit hinzufügen .xml:
<dependency>
<groupId>io.reactivex</groupId>
<artifactId>rxjava</artifactId>
<version>${rx.java.version}</version>
</dependency>
Oder für ein Gradle-Projekt:
compile 'io.reactivex.rxjava:rxjava:x.y.z'
3. Funktionale reaktive Konzepte
Auf der einen Seite ist ** funktionales Programmieren das Erstellen von Software, indem reine Funktionen erstellt werden, die den gemeinsamen Zustand, veränderliche Daten und Nebenwirkungen vermeiden.
Andererseits ist reactive programming ein asynchrones Programmierparadigma, das sich mit Datenströmen und der Ausbreitung von Änderungen befasst.
Funktionale reaktive Programmierung bildet zusammen eine Kombination aus funktionalen und reaktiven Techniken, die einen eleganten Ansatz für die ereignisgesteuerte Programmierung darstellen können - mit Werten, die sich im Laufe der Zeit ändern und bei denen der Verbraucher auf die Daten reagiert, sobald sie hereinkommen.
Diese Technologie vereint verschiedene Implementierungen ihrer Kernprinzipien. Einige Autoren haben ein Dokument vorgelegt, in dem das gemeinsame Vokabular zur Beschreibung der neuen Art von Anwendungen definiert wird.
3.1. Reaktives Manifest
Das Reactive Manifesto ist ein Online-Dokument, das einen hohen Standard für Anwendungen in der Software-Entwicklungsindustrie darstellt. Einfach ausgedrückt, reaktive Systeme sind:
-
Responsive - Systeme sollten zeitnah reagieren
-
Message Driven - Systeme sollten asynchrone Nachrichtenübermittlung verwenden
Komponenten, um eine lose Verbindung zu gewährleisten ** Elastic - Systeme sollten unter hoher Belastung reagieren
-
Resilient - Systeme sollten reagieren, wenn einige Komponenten ausfallen
4. Observables
Bei der Arbeit mit Rx gibt es zwei Haupttypen:
-
Observable steht für jedes Objekt, das Daten von Daten abrufen kann
Quelle und deren Zustand kann in einer Weise von Interesse sein, dass andere Objekte kann ein Interesse registrieren ** Ein observer ist ein Objekt, das benachrichtigt werden soll, wenn der Status vorliegt
eines anderen Objekts ändert sich
Ein observer abonniert eine Observable -Sequenz. Die Sequenz sendet Elemente nacheinander an den observer .
Der observer behandelt jeden, bevor der nächste verarbeitet wird. Wenn viele Ereignisse asynchron eintreffen, müssen sie in einer Warteschlange gespeichert oder gelöscht werden.
In Rx wird ein observer nie mit einem Artikel außerhalb der Reihenfolge aufgerufen oder aufgerufen, bevor der Rückruf für den vorherigen Artikel zurückgegeben wurde.
** 4.1. Observable -Typen
Es gibt zwei Arten:
-
Non-Blocking – Die asynchrone Ausführung wird unterstützt und darf
kündigen Sie an einem beliebigen Punkt im Ereignisstrom ab. In diesem Artikel konzentrieren wir uns hauptsächlich auf diesen Typ
-
Blocking – Alle onNext -Observer-Aufrufe sind synchron und
Es ist nicht möglich, das Abonnement mitten in einem Ereignisstrom abzubestellen. Wir können ein Observable immer in ein Blocking Observable konvertieren, indem wir die Methode toBlocking verwenden:
BlockingObservable<String> blockingObservable = observable.toBlocking();
4.2. Betreiber
-
Ein operator ist eine Funktion, die ein Observable (die Quelle) als erstes Argument verwendet und ein anderes Observable (das Ziel) zurückgibt. ** Dann wird für jedes Element, das das beobachtbare Quellobjekt ausgibt, eine Funktion auf dieses Element angewendet und anschließend ausgegeben das Ergebnis am Ziel Observable .
Operators können miteinander verkettet werden, um komplexe Datenflüsse zu erzeugen, die Ereignisse nach bestimmten Kriterien filtern. Mehrere Operatoren können auf dasselbe observable angewendet werden.
Es ist nicht schwierig, in eine Situation zu geraten, in der ein Observable Elemente schneller ausgibt, als ein operator oder observer sie verbrauchen kann.
Sie können mehr über den Gegendruck-Link lesen:/rxjava-backpressure[hier.]
4.3. Observable erstellen
Der Basisoperator just erzeugt ein Observable , das eine einzelne generische Instanz ausgibt, bevor der Vorgang abgeschlossen wird, der String __ "Hello".
Observable<String> observable = Observable.just("Hello");
observable.subscribe(s -> result = s);
assertTrue(result.equals("Hello"));
4.4. OnNext, OnError, und OnCompleted
Es gibt drei Methoden für die observer -Schnittstelle, über die wir wissen möchten:
-
OnNext wird bei jedem neuen Ereignis auf unserem observer aufgerufen
im angehängten Observable veröffentlicht. Dies ist die Methode, bei der wir Führen Sie für jedes Ereignis eine Aktion aus . OnCompleted wird aufgerufen, wenn die mit einem Ereignis verbundene Folge von Ereignissen ist
Observable ist abgeschlossen, was bedeutet, dass wir nicht mehr erwarten dürfen onNext ruft unseren Beobachter auf . OnError wird aufgerufen, wenn während des Befehls eine nicht behandelte Ausnahme ausgelöst wird
RxJava -Framework-Code oder unser Code für die Ereignisbehandlung
Der Rückgabewert für die Observables subscribe -Methode ist eine subscribe -Schnittstelle:
String[]letters = {"a", "b", "c", "d", "e", "f", "g"};
Observable<String> observable = Observable.from(letters);
observable.subscribe(
i -> result += i, //OnNext
Throwable::printStackTrace,//OnError
() -> result += "__Completed"//OnCompleted
);
assertTrue(result.equals("abcdefg__Completed"));
5. Beobachtbare Transformationen und bedingte Operatoren
5.1. Karte
Der m _ap-Operator wandelt Elemente um, die von einem Observable_ ausgegeben werden, indem auf jedes Element eine Funktion angewendet wird.
Nehmen wir an, es gibt ein deklariertes Array von Strings, das einige Buchstaben aus dem Alphabet enthält, und wir möchten sie im Großbuchstabenmodus drucken:
Observable.from(letters)
.map(String::toUpperCase)
.subscribe(letter -> result += letter);
assertTrue(result.equals("ABCDEFG"));
The flatMap kann verwendet werden, um Observables zu reduzieren, wenn wir mit verschachtelten __Observables enden.
Weitere Details zum Unterschied zwischen map und flatMap finden Sie unter hier .
Angenommen, wir haben eine Methode, die einen Observable <String> aus einer Liste von Strings zurückgibt. Jetzt drucken wir für jeden String aus einem neuen Observable die Liste der Titel basierend auf dem, was Subscriber sieht:
Observable<String> getTitle() {
return Observable.from(titleList);
}
Observable.just("book1", "book2")
.flatMap(s -> getTitle())
.subscribe(l -> result += l);
assertTrue(result.equals("titletitle"));
5.2. Scan
Der _scan-Operator a weist jedem Element, das von einem Observable_ nacheinander gesendet wird, eine Funktion zu und gibt jeden nachfolgenden Wert aus.
Es erlaubt uns, den Zustand von Ereignis zu Ereignis zu übertragen:
String[]letters = {"a", "b", "c"};
Observable.from(letters)
.scan(new StringBuilder(), StringBuilder::append)
.subscribe(total -> result += total.toString());
assertTrue(result.equals("aababc"));
5.3. Gruppiere nach
Gruppieren nach Operator ermöglicht das Klassifizieren der Ereignisse in der Eingabe Observable in Ausgabekategorien.
Nehmen wir an, wir haben ein Array von Ganzzahlen von 0 bis 10 erstellt und wenden dann group by an, um sie in die Kategorien even und odd zu unterteilen:
Observable.from(numbers)
.groupBy(i -> 0 == (i % 2) ? "EVEN" : "ODD")
.subscribe(group ->
group.subscribe((number) -> {
if (group.getKey().toString().equals("EVEN")) {
EVEN[0]+= number;
} else {
ODD[0]+= number;
}
})
);
assertTrue(EVEN[0].equals("0246810"));
assertTrue(ODD[0].equals("13579"));
5.4. Filter
Der Operator filter gibt nur die Elemente aus einem observable aus, die einen predicate -Test bestehen.
Lassen Sie uns also in einem ganzzahligen Array nach ungeraden Zahlen filtern:
Observable.from(numbers)
.filter(i -> (i % 2 == 1))
.subscribe(i -> result += i);
assertTrue(result.equals("13579"));
5.5. Bedingte Operatoren
DefaultIfEmpty gibt ein Element aus der Quelle Observable oder ein Standardelement aus, wenn die Quelle Observable leer ist:
Observable.empty()
.defaultIfEmpty("Observable is empty")
.subscribe(s -> result += s);
assertTrue(result.equals("Observable is empty"));
Der folgende Code gibt den ersten Buchstaben des Alphabets ' a' aus, da das Array letters__ nicht leer ist und dies an der ersten Stelle enthalten ist:
Observable.from(letters)
.defaultIfEmpty("Observable is empty")
.first()
.subscribe(s -> result += s);
assertTrue(result.equals("a"));
Der TakeWhile -Operator verwirft Elemente, die von Observable ausgegeben werden, nachdem eine angegebene Bedingung zu "false" wird:
Observable.from(numbers)
.takeWhile(i -> i < 5)
.subscribe(s -> sum[0]+= s);
assertTrue(sum[0]== 10);
Natürlich gibt es noch weitere Operatoren, die unsere Bedürfnisse abdecken könnten, wie Contain, SkipWhile, SkipUntil, TakeUntil, usw.
6. Anschließbare Observables
-
Ein ConnectableObservable ähnelt einem normalen Observable , nur dass es keine Elemente ausgibt, wenn es abonniert wird, sondern nur, wenn der connect -Operator darauf angewendet wird. **
Auf diese Weise können wir warten, bis alle vorgesehenen Beobachter den Observable abonniert haben, bevor der Observable Elemente ausgibt:
String[]result = {""};
ConnectableObservable<Long> connectable
= Observable.interval(200, TimeUnit.MILLISECONDS).publish();
connectable.subscribe(i -> result[0]+= i);
assertFalse(result[0].equals("01"));
connectable.connect();
Thread.sleep(500);
assertTrue(result[0].equals("01"));
7. Single
Single ist wie ein Observable , der anstelle einer Reihe von Werten einen Wert oder eine Fehlerbenachrichtigung ausgibt.
Mit dieser Datenquelle können wir zum Abonnieren nur zwei Methoden verwenden:
-
OnSuccess gibt ein Single zurück, das auch eine von uns angegebene Methode aufruft
-
OnError gibt auch ein Single zurück, das sofort benachrichtigt wird
Abonnenten eines Fehlers
String[]result = {""};
Single<String> single = Observable.just("Hello")
.toSingle()
.doOnSuccess(i -> result[0]+= i)
.doOnError(error -> {
throw new RuntimeException(error.getMessage());
});
single.subscribe();
assertTrue(result[0].equals("Hello"));
8. Themen
Ein Subject ist gleichzeitig zwei Elemente, ein subscriber und ein observable . Als Abonnent kann ein Thema verwendet werden, um die Ereignisse zu veröffentlichen, die von mehr als einem beobachtbaren Ereignis stammen.
Und weil es auch beobachtbar ist, können die Ereignisse mehrerer Abonnenten für jeden Beobachter als Ereignisse wiedergegeben werden
Im nächsten Beispiel werden wir sehen, wie die Beobachter die Ereignisse sehen können, die nach ihrer Anmeldung auftreten:
Integer subscriber1 = 0;
Integer subscriber2 = 0;
Observer<Integer> getFirstObserver() {
return new Observer<Integer>() {
@Override
public void onNext(Integer value) {
subscriber1 += value;
}
@Override
public void onError(Throwable e) {
System.out.println("error");
}
@Override
public void onCompleted() {
System.out.println("Subscriber1 completed");
}
};
}
Observer<Integer> getSecondObserver() {
return new Observer<Integer>() {
@Override
public void onNext(Integer value) {
subscriber2 += value;
}
@Override
public void onError(Throwable e) {
System.out.println("error");
}
@Override
public void onCompleted() {
System.out.println("Subscriber2 completed");
}
};
}
PublishSubject<Integer> subject = PublishSubject.create();
subject.subscribe(getFirstObserver());
subject.onNext(1);
subject.onNext(2);
subject.onNext(3);
subject.subscribe(getSecondObserver());
subject.onNext(4);
subject.onCompleted();
assertTrue(subscriber1 + subscriber2 == 14)
9. Resourcenmanagement
Durch den Using -Vorgang können wir Ressourcen wie eine JDBC-Datenbankverbindung, eine Netzwerkverbindung oder offene Dateien unseren Observables zuordnen.
Hier stellen wir Ihnen in Kommentaren die Schritte vor, die erforderlich sind, um dieses Ziel zu erreichen, sowie ein Implementierungsbeispiel:
String[]result = {""};
Observable<Character> values = Observable.using(
() -> "MyResource",
r -> {
return Observable.create(o -> {
for (Character c : r.toCharArray()) {
o.onNext(c);
}
o.onCompleted();
});
},
r -> System.out.println("Disposed: " + r)
);
values.subscribe(
v -> result[0]+= v,
e -> result[0]+= e
);
assertTrue(result[0].equals("MyResource"));
10. Fazit
In diesem Artikel haben wir die Verwendung der RxJava-Bibliothek und die Erkundung der wichtigsten Funktionen erläutert.
Den vollständigen Quellcode für das Projekt einschließlich aller hier verwendeten Codebeispiele finden Sie unter https://github.com/eugenp/tutorials/tree/master/rxjava