Wann verwenden Sie map vs flatMap in RxJava?

180

Wann verwenden Sie mapvs flatMapin RxJava ?

Angenommen, wir möchten Dateien, die JSON enthalten, Strings zuordnen, die JSON enthalten.

Mit mapmüssen wir uns Exceptionirgendwie mit dem auseinandersetzen . Aber wie?:

Observable.from(jsonFile).map(new Func1<File, String>() {
    @Override public String call(File file) {
        try {
            return new Gson().toJson(new FileReader(file), Object.class);
        } catch (FileNotFoundException e) {
            // So Exception. What to do ?
        }
        return null; // Not good :(
    }
});

Mit flatMapist es viel ausführlicher, aber wir können das Problem in der gesamten Kette weiterleiten Observablesund den Fehler behandeln, wenn wir uns für einen anderen Ort entscheiden und es sogar erneut versuchen:

Observable.from(jsonFile).flatMap(new Func1<File, Observable<String>>() {
    @Override public Observable<String> call(final File file) {
        return Observable.create(new Observable.OnSubscribe<String>() {
            @Override public void call(Subscriber<? super String> subscriber) {
                try {
                    String json = new Gson().toJson(new FileReader(file), Object.class);

                    subscriber.onNext(json);
                    subscriber.onCompleted();
                } catch (FileNotFoundException e) {
                    subscriber.onError(e);
                }
            }
        });
    }
});

Ich mag die Einfachheit der map, aber die Fehlerbehandlung von flatmap(nicht die Ausführlichkeit). Ich habe keine Best Practices für dieses Herumschweben gesehen und bin gespannt, wie dies in der Praxis angewendet wird.

Christopher Perry
quelle

Antworten:

121

mapein Ereignis in ein anderes umwandeln. flatMapein Ereignis in ein Ereignis von null oder mehr umwandeln. (Dies ist aus IntroToRx entnommen )

Wenn Sie Ihren JSON in ein Objekt umwandeln möchten, sollte die Verwendung von Map ausreichen.

Der Umgang mit der FileNotFoundException ist ein weiteres Problem (die Verwendung von Map oder Flatmap würde dieses Problem nicht lösen).

Um Ihr Ausnahmeproblem zu lösen, werfen Sie es einfach mit einer nicht aktivierten Ausnahme: RX ruft den onError-Handler für Sie auf.

Observable.from(jsonFile).map(new Func1<File, String>() {
    @Override public String call(File file) {
        try {
            return new Gson().toJson(new FileReader(file), Object.class);
        } catch (FileNotFoundException e) {
            // this exception is a part of rx-java
            throw OnErrorThrowable.addValueAsLastCause(e, file);
        }
    }
});

die exakt gleiche Version mit Flatmap:

Observable.from(jsonFile).flatMap(new Func1<File, Observable<String>>() {
    @Override public Observable<String> call(File file) {
        try {
            return Observable.just(new Gson().toJson(new FileReader(file), Object.class));
        } catch (FileNotFoundException e) {
            // this static method is a part of rx-java. It will return an exception which is associated to the value.
            throw OnErrorThrowable.addValueAsLastCause(e, file);
            // alternatively, you can return Obersable.empty(); instead of throwing exception
        }
    }
});

Sie können in der flatMap-Version auch eine neue Observable zurückgeben, die nur ein Fehler ist.

Observable.from(jsonFile).flatMap(new Func1<File, Observable<String>>() {
    @Override public Observable<String> call(File file) {
        try {
            return Observable.just(new Gson().toJson(new FileReader(file), Object.class));
        } catch (FileNotFoundException e) {
            return Observable.error(OnErrorThrowable.addValueAsLastCause(e, file));
        }
    }
});
dwursteisen
quelle
2
Dies ruft nicht auf subscriber.onError()usw. Alle Beispiele, die ich gesehen habe, haben Fehler auf diese Weise weitergeleitet. Ist das nicht wichtig?
Christopher Perry
7
Beachten Sie, dass die Konstruktoren von OnErrorThrowableare sind privateund Sie OnErrorThrowable.from(e)stattdessen verwenden müssen.
David.mihola
Ich habe gerade aktualisiert. OnErrorThrowable.from (e) behält den Wert nicht bei, daher verwende ich stattdessen OnErrorThrowable.addValueAsLastCause (e, Datei), wodurch der Wert beibehalten werden soll.
Dwursteisen
1
Ich mag die Codebeispiele, aber es wäre hilfreich, wenn Sie die Signatur der flatMap-Aufrufe aktualisieren würden, um Observable <String> anstelle von nur String zurückzugeben ... denn ist das technisch nicht der Unterschied zwischen den beiden?
Rich Ehmer
78

FlatMap verhält sich sehr ähnlich wie Map, der Unterschied besteht darin, dass die Funktion angewendet wird ein Observable selbst zurückgibt, sodass es perfekt für die Zuordnung über asynchrone Operationen geeignet ist.

Im praktischen Sinne führt die Funktion Map nur eine Transformation über die verkettete Antwort durch (ohne ein Observable zurückzugeben). während die Funktion FlatMap angewendet wird, wird ein zurückgegebenObservable<T> angewendet wird, wird . Aus diesem Grund wird FlatMap empfohlen, wenn Sie einen asynchronen Aufruf innerhalb der Methode planen.

Zusammenfassung:

  • Map gibt ein Objekt vom Typ T zurück
  • FlatMap gibt ein Observable zurück.

Ein klares Beispiel finden Sie hier: http://blog.couchbase.com/why-couchbase-chose-rxjava-new-java-sdk .

Der Couchbase Java 2.X-Client verwendet Rx, um auf bequeme Weise asynchrone Anrufe bereitzustellen. Da Rx verwendet wird, die Methoden map und FlatMap vorhanden sind, kann die Erläuterung in der Dokumentation hilfreich sein, um das allgemeine Konzept zu verstehen.

Um Fehler zu behandeln, überschreiben Sie onError auf Ihrem Susbcriber.

Subscriber<String> mySubscriber = new Subscriber<String>() {
    @Override
    public void onNext(String s) { System.out.println(s); }

    @Override
    public void onCompleted() { }

    @Override
    public void onError(Throwable e) { }
};

Es könnte hilfreich sein, sich dieses Dokument anzusehen: http://blog.danlew.net/2014/09/15/grokking-rxjava-part-1/

Eine gute Quelle zum Verwalten von Fehlern mit RX finden Sie unter: https://gist.github.com/daschl/db9fcc9d2b932115b679

1vand1ng0
quelle
Die Zusammenfassung ist falsch. Map und FlatMap geben denselben Typ zurück, aber die von ihnen angewendete Funktion gibt einen anderen Typ zurück.
CoXier
61

In Ihrem Fall benötigen Sie eine Karte, da es nur 1 Eingabe und 1 Ausgabe gibt.

Die von map bereitgestellte Funktion akzeptiert einfach einen Artikel und gibt einen Artikel zurück, der weiter unten (nur einmal) ausgegeben wird.

Die von flatMap bereitgestellte Funktion akzeptiert ein Element und gibt dann ein "Observable" zurück, was bedeutet, dass jedes Element des neuen "Observable" weiter unten separat ausgegeben wird.

Möglicherweise klärt Code die Dinge für Sie auf:

Observable.just("item1").map( str -> {
    System.out.println("inside the map " + str);
    return str;
}).subscribe(System.out::println);

Observable.just("item2").flatMap( str -> {
    System.out.println("inside the flatMap " + str);
    return Observable.just(str + "+", str + "++" , str + "+++");
}).subscribe(System.out::println);

Ausgabe:

inside the map item1
item1
inside the flatMap item2
item2+
item2++
item2+++
mt.uulu
quelle
Ich bin mir nicht sicher, ob die Verwendung einer Karte die beste Idee ist, obwohl sie funktionieren würde. Angenommen, der FileReader sollte ein asynchroner Aufruf werden. Dann müssten Sie die Karte in eine flatMap ändern. Wenn Sie es als Karte belassen, bedeutet dies, dass Ereignisse nicht wie erwartet ausgelöst werden und Verwirrung stiften. Ich bin ein paar Mal davon gebissen worden, als ich noch RX Java lerne. Ich finde, flatMap ist ein sicherer Weg, um sicherzustellen, dass die Dinge so verarbeitet werden, wie Sie es erwarten.
user924272
24

Ich denke darüber nach, dass Sie verwenden, flatMapwenn die Funktion, die Sie in setzen map()möchten, eine zurückgibt Observable. In diesem Fall könnten Sie immer noch versuchen, es zu verwenden, map()aber es wäre unpraktisch. Lassen Sie mich versuchen zu erklären, warum.

Wenn Sie sich in einem solchen Fall dazu entschließen, bei zu bleiben map, würden Sie eine bekommen Observable<Observable<Something>>. Wenn wir in Ihrem Fall beispielsweise eine imaginäre RxGson-Bibliothek verwenden, die eine Methode Observable<String>aus ihrer toJson()Methode zurückgibt (anstatt einfach eine zurückzugeben String), sieht dies folgendermaßen aus:

Observable.from(jsonFile).map(new Func1<File, Observable<String>>() {
    @Override public Observable<String>> call(File file) {
        return new RxGson().toJson(new FileReader(file), Object.class);
    }
}); // you get Observable<Observable<String>> here

An diesem Punkt wäre es ziemlich schwierig, subscribe()eine solche Beobachtung zu machen. Darin würden Sie eine erhalten, Observable<String>zu der Sie erneut subscribe()den Wert benötigen würden . Welches ist nicht praktisch oder schön anzusehen.

Um es nützlich zu machen, besteht eine Idee darin, dieses Observable von Observables zu "glätten" (Sie könnten anfangen zu sehen, woher der Name _flat_Map kommt). RxJava bietet einige Möglichkeiten, um Observable zu reduzieren. Der Einfachheit halber nehmen wir an, dass das Zusammenführen das ist, was wir wollen. Beim Zusammenführen werden im Grunde genommen eine Reihe von Observablen verwendet und immer dann ausgegeben, wenn eine von ihnen ausgegeben wird. (Viele Leute würden argumentieren , dass ein Wechsel eine bessere Standardeinstellung wäre. Wenn Sie jedoch nur einen Wert ausgeben, spielt dies keine Rolle.)

Wenn wir also unser vorheriges Snippet ändern, erhalten wir:

Observable.from(jsonFile).map(new Func1<File, Observable<String>>() {
    @Override public Observable<String>> call(File file) {
        return new RxGson().toJson(new FileReader(file), Object.class);
    }
}).merge(); // you get Observable<String> here

Dies ist viel nützlicher, da Sie beim Abonnieren (oder Zuordnen oder Filtern oder ...) nur den StringWert erhalten. ( merge()Wohlgemerkt , eine solche Variante von gibt es in RxJava nicht, aber wenn Sie die Idee der Zusammenführung verstehen, dann hoffe ich, dass Sie auch verstehen, wie das funktionieren würde.)

Also im Grunde genommen, weil solche merge()wahrscheinlich immer nur dann nützlich sein sollten, wenn es gelingt map(), ein Observable zurückzugeben, und Sie dies nicht immer wieder flatMap()eingeben müssen , wurde es als Abkürzung erstellt. Die Zuordnungsfunktion wird wie gewohnt map()angewendet, aber später werden die zurückgegebenen Werte nicht mehr ausgegeben, sondern "abgeflacht" (oder zusammengeführt).

Das ist der allgemeine Anwendungsfall. Dies ist am nützlichsten in einer Codebasis, die überall Rx verwendet, und Sie haben viele Methoden, die Observables zurückgeben, die Sie mit anderen Methoden verketten möchten, die Observables zurückgeben.

In Ihrem Anwendungsfall ist dies ebenfalls nützlich, da map()nur ein emittierter Wert onNext()in einen anderen emittierten Wert umgewandelt werden kann onNext(). Es kann jedoch nicht in mehrere Werte umgewandelt werden, überhaupt keinen Wert oder einen Fehler. Und wie akarnokd in seiner Antwort schrieb (und wohlgemerkt , er ist viel schlauer als ich, wahrscheinlich im Allgemeinen, aber zumindest wenn es um RxJava geht), sollten Sie keine Ausnahmen von Ihrer machen map(). Also stattdessen können Sie flatMap()und verwenden

return Observable.just(value);

wenn alles gut geht, aber

return Observable.error(exception);

wenn etwas ausfällt.
In seiner Antwort finden Sie einen vollständigen Ausschnitt: https://stackoverflow.com/a/30330772/1402641

Marcin Koziński
quelle
1
Das ist meine bevorzugte Antwort. Im Grunde verschachteln Sie ein beobachtbares IN in ein beobachtbares WENN, das Ihre Methode zurückgibt.
filthy_wizard
21

Die Frage ist, wann Sie map vs flatMap in RxJava verwenden. . Und ich denke, eine einfache Demo ist spezifischer.

Wenn Sie ein ausgegebenes Element in einen anderen Typ konvertieren möchten, können in Ihrem Fall das Konvertieren von Dateien in String, Map und FlatMap funktionieren. Aber ich bevorzuge Kartenoperatoren, weil es klarer ist.

flatMapKann jedoch irgendwo magische Arbeit leisten , kann es aber mapnicht. Zum Beispiel möchte ich die Informationen eines Benutzers erhalten, aber ich muss zuerst seine ID erhalten, wenn sich der Benutzer anmeldet. Natürlich benötige ich zwei Anfragen und sie sind in Ordnung.

Lass uns anfangen.

Observable<LoginResponse> login(String email, String password);

Observable<UserInfo> fetchUserInfo(String userId);

Hier sind zwei Methoden, eine zum Zurückgeben der Anmeldung Responseund eine zum Abrufen von Benutzerinformationen.

login(email, password)
        .flatMap(response ->
                fetchUserInfo(response.id))
        .subscribe(userInfo -> {
            // get user info and you update ui now
        });

Wie Sie sehen, wird in der Funktion flatMap zuerst die Benutzer-ID abgerufen und Responsedann die Benutzerinformationen abgerufen. Wenn zwei Anforderungen abgeschlossen sind, können wir unsere Aufgabe erledigen, z. B. die Benutzeroberfläche aktualisieren oder Daten in einer Datenbank speichern.

Wenn Sie jedoch verwenden map, können Sie keinen so schönen Code schreiben. Mit einem Wort, flatMapkann uns helfen, Anfragen zu serialisieren.

CoXier
quelle
18

Hier ist eine einfache Daumenregel , die ich verwende, um zu entscheiden, wann ich flatMap()sie map()in Rx verwenden sollObservable .

Sobald Sie zu einer Entscheidung kommen, dass Sie eine beschäftigen werden map Transformation entschieden haben, schreiben Sie Ihren Transformationscode, um ein Objekt zurückzugeben, oder?

Wenn Sie als Endergebnis Ihrer Transformation Folgendes zurückgeben:

  • ein nicht beobachtbares Objekt, das Sie dann nur verwenden würdenmap() . Und map()verpackt dieses Objekt in ein Observable und sendet es aus.

  • ein ObservableObjekt, dann würden Sie verwendenflatMap() . Und flatMap()packt das Observable aus, wählt das zurückgegebene Objekt aus, umhüllt es mit seinem eigenen Observable und gibt es aus.

Angenommen, wir haben eine Methode titleCase (String inputParam), die das Titled Cased String-Objekt des Eingabeparameters zurückgibt. Der Rückgabetyp dieser Methode kann Stringoder sein Observable<String>.

  • Wenn der Rückgabetyp von titleCase(..)nur wäre String, würden Sie verwendenmap(s -> titleCase(s))

  • Wenn der Rückgabetyp von titleCase(..)wäre Observable<String>, würden Sie verwendenflatMap(s -> titleCase(s))

Hoffe das klärt.

karthiks
quelle
11

Ich wollte nur hinzufügen, dass flatMapSie nicht wirklich Ihre eigene Observable innerhalb der Funktion verwenden müssen und sich auf Standard-Factory-Methoden / -Operatoren verlassen können:

Observable.from(jsonFile).flatMap(new Func1<File, Observable<String>>() {
    @Override public Observable<String> call(final File file) {
        try {
            String json = new Gson().toJson(new FileReader(file), Object.class);
            return Observable.just(json);
        } catch (FileNotFoundException ex) {
            return Observable.<String>error(ex);
        }
    }
});

Im Allgemeinen sollten Sie nach Möglichkeit vermeiden, (Laufzeit-) Ausnahmen von onXXX-Methoden und -Rückrufen auszulösen, obwohl wir in RxJava so viele Sicherheitsvorkehrungen wie möglich getroffen haben.

akarnokd
quelle
Aber ich denke Karte ist genug. FlatMap und Map sind also eine Gewohnheit, oder?
CoXier
6

In diesem Szenario verwenden Sie Map, Sie benötigen kein neues Observable dafür.

Sie sollten Exceptions.propagate verwenden, einen Wrapper, damit Sie diese überprüften Ausnahmen an den Empfangsmechanismus senden können

Observable<String> obs = Observable.from(jsonFile).map(new Func1<File, String>() { 
    @Override public String call(File file) {
        try { 
            return new Gson().toJson(new FileReader(file), Object.class);
        } catch (FileNotFoundException e) {
            throw Exceptions.propagate(t); /will propagate it as error
        } 
    } 
});

Sie sollten diesen Fehler dann im Abonnenten behandeln

obs.subscribe(new Subscriber<String>() {
    @Override 
    public void onNext(String s) { //valid result }

    @Override 
    public void onCompleted() { } 

    @Override 
    public void onError(Throwable e) { //e might be the FileNotFoundException you got }
};); 

Es gibt einen ausgezeichneten Beitrag dafür: http://blog.danlew.net/2015/12/08/error-handling-in-rxjava/

ndori
quelle
0

In einigen Fällen haben Sie möglicherweise eine Kette von Observablen, wobei Ihre Observable eine andere Observable zurückgibt. 'flatmap' packt die zweite Observable aus, die in der ersten vergraben ist, und lässt Sie direkt auf die Daten zugreifen, die die zweite Observable beim Abonnieren ausspuckt.

Anoop Isaac
quelle
0

Flatmap ordnet Observablen Observablen zu. Map ordnet Elemente Elementen zu.

Flatmap ist flexibler, aber Map ist leichter und direkter, sodass es von Ihrem Anwendungsfall abhängt.

Wenn Sie ALLES asynchron ausführen (einschließlich Thread-Wechsel), sollten Sie Flatmap verwenden, da Map nicht prüft, ob der Verbraucher bereit ist (Teil der Leichtigkeit).

skr1p7k1dd
quelle