Einführung in RxJava

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:

  1. 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