Quando usi map vs flatMap in RxJava?


180

Quando usi mapvs flatMapin RxJava ?

Supponiamo, ad esempio, che vogliamo mappare i file contenenti JSON in stringhe che contengono JSON:

Usando map, dobbiamo occuparci in Exceptionqualche modo. Ma come?:

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 :(
    }
});

Utilizzando flatMap, è molto più dettagliato, ma possiamo inoltrare il problema lungo la catena di Observablese gestire l'errore se scegliamo altrove e anche riprovare:

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);
                }
            }
        });
    }
});

Mi piace la semplicità di map, ma la gestione degli errori di flatmap(non la verbosità). Non ho visto nessuna delle migliori pratiche su questo fluttuare in giro e sono curioso di sapere come questo viene utilizzato in pratica.

Risposte:


121

maptrasforma un evento in un altro. flatMaptrasforma un evento in zero o più eventi. (questo è tratto da IntroToRx )

Se vuoi trasformare il tuo json in un oggetto, usare map dovrebbe essere sufficiente.

Trattare con FileNotFoundException è un altro problema (l'uso di map o flatmap non risolverà questo problema).

Per risolvere il problema dell'eccezione, basta lanciarlo con un'eccezione Non selezionata: RX chiamerà il gestore onError per te.

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);
        }
    }
});

la stessa identica versione con 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
        }
    }
});

Puoi anche restituire, nella versione flatMap un nuovo osservabile che è solo un errore.

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));
        }
    }
});

2
Questo non chiama subscriber.onError()ecc. Tutti gli esempi che ho visto hanno instradato errori in questo modo. Non importa?
Christopher Perry,

7
Nota che i costruttori di OnErrorThrowablesono privatee devi OnErrorThrowable.from(e)invece usarli .
david.mihola,

Ho appena aggiornato. OnErrorThrowable.from (e) non mantiene il valore, quindi uso OnErrorThrowable.addValueAsLastCause (e, file), che dovrebbe mantenere il valore.
Dwursteisen,

1
Mi piacciono gli esempi di codice, ma sarebbe utile se aggiorni la firma delle chiamate flatMap per restituire Observable <String> anziché solo String ... perché non è tecnicamente la differenza tra i due?
Rich Ehmer,

78

FlatMap si comporta in modo molto simile a map, la differenza è che la funzione che applica restituisce se stessa osservabile, quindi è perfettamente adatta per mappare su operazioni asincrone.

In senso pratico, la funzione Mappa applicata si limita a trasformare la risposta incatenata (non restituendo un osservabile); mentre la funzione FlatMap applica restituisce un Observable<T>, ecco perché si consiglia FlatMap se si prevede di effettuare una chiamata asincrona all'interno del metodo.

Sommario:

  • Mappa restituisce un oggetto di tipo T.
  • FlatMap restituisce un osservabile.

Un chiaro esempio può essere visto qui: http://blog.couchbase.com/why-couchbase-chose-rxjava-new-java-sdk .

Couchbase Java 2.X Client utilizza Rx per fornire chiamate asincrone in modo conveniente. Dal momento che utilizza Rx, ha la mappa dei metodi e FlatMap, la spiegazione nella loro documentazione potrebbe essere utile per comprendere il concetto generale.

Per gestire gli errori, sovrascrivi onError sul tuo 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) { }
};

Potrebbe essere utile consultare questo documento: http://blog.danlew.net/2014/09/15/grokking-rxjava-part-1/

Una buona fonte su come gestire gli errori con RX è disponibile all'indirizzo: https://gist.github.com/daschl/db9fcc9d2b932115b679


Il riassunto è sbagliato Map e FlatMap restituiscono lo stesso tipo ma la funzione che applicano restituisce un tipo diverso.
CoXier

61

Nel tuo caso hai bisogno di una mappa, poiché c'è solo 1 input e 1 output.

la funzione mappa fornita accetta semplicemente un articolo e restituisce un oggetto che verrà emesso ulteriormente (solo una volta) verso il basso.

flatMap - la funzione fornita accetta un oggetto, quindi restituisce un "Osservabile", il che significa che ogni elemento del nuovo "Osservabile" verrà emesso separatamente più in basso.

Potrebbe essere il codice chiarirà le cose per te:

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);

Produzione:

inside the map item1
item1
inside the flatMap item2
item2+
item2++
item2+++

Non sono sicuro se l'utilizzo di una mappa sia l'idea migliore, anche se funzionerebbe. Supponiamo che il FileReader diventasse una chiamata asincrona. Quindi dovrai cambiare la mappa in una mappa piatta. Lasciarlo come una mappa significherebbe che non verrebbero attivati ​​eventi come previsto e causerebbe confusione. Sono stato morso da questo alcune volte mentre sto ancora imparando RX Java. Trovo flatMap è un modo sicuro per garantire che le cose vengano elaborate come previsto.
user924272,

24

Il modo in cui ci penso è che usi flatMapquando la funzione che volevi mettere dentro map()restituisce un Observable. Nel qual caso potresti comunque provare a usare map()ma non sarebbe pratico. Vorrei provare a spiegare il perché.

Se in tal caso decidessi di restare map, otterrai un Observable<Observable<Something>>. Ad esempio, nel tuo caso, se usassimo una libreria RxGson immaginaria, che restituiva un metodo Observable<String>dal suo toJson()(invece di restituire semplicemente a String) sarebbe simile a questo:

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

A questo punto sarebbe piuttosto complicato osservarlo subscribe(). Al suo interno si otterrebbe un valore Observable<String>a cui sarebbe nuovamente necessario subscribe()ottenere il valore. Che non è pratico o bello da vedere.

Quindi per rendere utile un'idea è "appiattire" questo osservabile di osservabili (potresti iniziare a vedere da dove viene il nome _flat_Map). RxJava offre alcuni modi per appiattire osservabili e per semplicità supponiamo che l' unione sia ciò che vogliamo. L'unione fondamentalmente prende un sacco di osservabili ed emette ogni qualvolta uno di essi emette. (Molte persone sostengono che il cambio sarebbe un valore predefinito migliore. Ma se stai emettendo un solo valore, non importa comunque.)

Quindi modificando il nostro frammento precedente avremmo:

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

Questo è molto più utile, perché iscrivendoti a questo (o mappando, o filtrando, o ...) ottieni semplicemente il Stringvalore. ( merge()Intendiamoci , tale variante di non esiste in RxJava, ma se capisci l'idea di unire, spero che capirai anche come funzionerebbe.)

Quindi, in sostanza, perché merge()probabilmente dovrebbe essere utile solo quando riesce a map()restituire un osservabile e quindi non è necessario digitarlo più volte, è flatMap()stato creato come una scorciatoia. Applica la funzione di mappatura come map()farebbe normalmente , ma in seguito invece di emettere i valori restituiti li "appiattisce" (o li unisce).

Questo è il caso d'uso generale. È molto utile in una base di codice che utilizza Rx allo stesso posto e hai molti metodi che restituiscono osservabili, che vuoi concatenare con altri metodi che restituiscono osservabili.

Nel tuo caso d'uso sembra essere utile, perché map()può trasformare solo un valore emesso onNext()in un altro valore emesso onNext(). Ma non può trasformarlo in più valori, nessun valore o errore. E come ha scritto akarnokd nella sua risposta (e attenzione che è molto più intelligente di me, probabilmente in generale, ma almeno quando si tratta di RxJava) non dovresti gettare eccezioni dalla tua map(). Quindi invece puoi usare flatMap()e

return Observable.just(value);

quando tutto va bene, ma

return Observable.error(exception);

quando qualcosa fallisce.
Guarda la sua risposta per uno snippet completo: https://stackoverflow.com/a/30330772/1402641


1
questa è la mia risposta preferita. fondamentalmente finisci per annidare un osservabile IN un osservabile che è ciò che il tuo metodo restituisce.
filthy_wizard,

21

La domanda è: quando usi map vs flatMap in RxJava? . E penso che una semplice demo sia più specifica.

Quando vuoi convertire un elemento emesso in un altro tipo, nel tuo caso la conversione di file in String, map e flatMap può funzionare entrambi. Ma preferisco l'operatore di mappe perché è più chiaro.

Comunque in qualche posto, flatMappuò fare un lavoro magico ma mapnon può. Ad esempio, voglio ottenere le informazioni di un utente, ma devo prima ottenere il suo ID quando l'utente effettua l'accesso. Ovviamente ho bisogno di due richieste e sono in ordine.

Cominciamo.

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

Observable<UserInfo> fetchUserInfo(String userId);

Ecco due metodi, uno per il login restituito Responsee un altro per il recupero delle informazioni utente.

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

Come vedi, nella funzione flatMap si applica, dapprima ottengo l'ID utente da cui Responsepoi recupero le informazioni dell'utente. Al termine di due richieste, possiamo svolgere il nostro lavoro come l'aggiornamento dell'interfaccia utente o il salvataggio dei dati nel database.

Tuttavia se lo usi mapnon puoi scrivere un codice così carino. In una parola, flatMappuò aiutarci a serializzare le richieste.


18

Ecco un semplice pollice regola che uso aiutarmi a decidere come quando da utilizzare flatMap()nel corso map()di Rx di Observable.

Una volta che prendi la decisione di utilizzare una maptrasformazione, scriveresti il ​​tuo codice di trasformazione per restituire un oggetto, giusto?

Se quello che stai restituendo come risultato finale della tua trasformazione è:

  • un oggetto non osservabile che useresti solomap() . E map()avvolge quell'oggetto in un osservabile e lo emette.

  • un Observableoggetto, quindi userestiflatMap() . E flatMap()riavvolge l'Osservabile, raccoglie l'oggetto restituito, lo avvolge con il suo Osservabile ed emette.

Supponiamo ad esempio che abbiamo un metodo titleCase (String inputParam) che restituisce l'oggetto String con titolo Cased del parametro di input. Il tipo di ritorno di questo metodo può essere Stringo Observable<String>.

  • Se il tipo di titleCase(..)reso fosse semplice String, allora userestimap(s -> titleCase(s))

  • Se il tipo restituito titleCase(..)fosse Observable<String>, allora userestiflatMap(s -> titleCase(s))

Spero che chiarisca.


11

Volevo solo aggiungerlo con flatMap, non hai davvero bisogno di usare il tuo osservabile personalizzato all'interno della funzione e puoi fare affidamento su metodi / operatori di fabbrica standard:

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);
        }
    }
});

In generale, dovresti evitare di gettare (Runtime-) eccezioni dai metodi e dai callback onXXX, se possibile, anche se abbiamo messo quante più garanzie possibili in RxJava.


Ma penso che la mappa sia abbastanza. Quindi flatMap e mappa sono un'abitudine giusto?
CoXier

6

In quello scenario utilizzare la mappa, non è necessario un nuovo osservabile per esso.

dovresti usare Exceptions.propagate, che è un wrapper in modo da poter inviare quelle eccezioni controllate al meccanismo rx

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
        } 
    } 
});

Dovresti quindi gestire questo errore nel sottoscrittore

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 }
};); 

C'è un post eccellente per questo: http://blog.danlew.net/2015/12/08/error-handling-in-rxjava/


0

In alcuni casi potresti finire per avere una catena di osservabili, in cui il tuo osservabile ne restituirebbe un altro osservabile. Il tipo "flatmap" disimballa il secondo osservabile che è sepolto nel primo e ti consente di accedere direttamente ai dati che il secondo osservabile sputa durante la sottoscrizione.


0

Flatmap mappa osservabili a osservabili. Mappa mappa gli articoli sugli articoli.

Flatmap è più flessibile ma Map è più leggero e diretto, quindi dipende dal tuo caso d'uso.

Se stai facendo QUALCOSA di asincrono (compresi i passaggi di commutazione), dovresti utilizzare Flatmap, poiché Map non controllerà se il consumatore è disposto (parte della leggerezza)

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.