Kiedy używasz map vs flatMap w RxJava?

180

Kiedy używasz mapvs flatMapw RxJava ?

Powiedzmy na przykład, że chcemy zmapować pliki zawierające JSON na ciągi zawierające JSON--

Używając map, musimy Exceptionjakoś sobie z tym poradzić . Ale jak?:

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

Używając flatMap, jest to znacznie bardziej szczegółowe, ale możemy przekazać problem w dół Observablesi obsłużyć błąd, jeśli wybierzemy gdzie indziej, a nawet spróbujemy:

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

Podoba mi się prostota obsługi map, ale obsługa błędów flatmap(nie gadatliwość). Nie widziałem żadnych dobrych praktyk dotyczących tego unoszenia się i jestem ciekawy, jak to jest wykorzystywane w praktyce.

Christopher Perry
źródło

Odpowiedzi:

121

mapprzekształcić jedno wydarzenie w drugie. flatMapprzekształcić jedno zdarzenie w zero lub więcej zdarzeń. (pochodzi z IntroToRx )

Jeśli chcesz przekształcić swój Json w obiekt, wystarczy użyć mapy.

Innym problemem jest radzenie sobie z wyjątkiem FileNotFoundException (użycie mapy lub płaskiej mapy nie rozwiązałoby tego problemu).

Aby rozwiązać problem wyjątku, po prostu wyrzuć go z Niezaznaczonym wyjątkiem: RX wywoła dla ciebie procedurę obsługi onError.

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

dokładnie ta sama wersja z płaską mapą:

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

Możesz również zwrócić, w wersji flatMap nowy Observable, który jest tylko błędem.

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
źródło
2
Nie wywołuje to subscriber.onError()itd. Wszystkie przykłady, które widziałem, spowodowały błędy w ten sposób. Czy to nie ma znaczenia?
Christopher Perry
7
Zauważ, że konstruktory OnErrorThrowableprivatei musisz ich użyć OnErrorThrowable.from(e).
david.mihola
Właśnie zaktualizowałem. OnErrorThrowable.from (e) nie zachowuje wartości, dlatego używam OnErrorThrowable.addValueAsLastCause (e, plik), który powinien zachować wartość.
dwursteisen
1
Podobają mi się przykłady kodu, ale pomogłoby to, gdybyś zaktualizował podpis wywołań flatMap w celu zwrócenia Observable <String> zamiast tylko String ... bo czy technicznie nie jest to różnica między nimi?
Rich Ehmer,
78

FlatMap zachowuje się bardzo podobnie do mapy, różnica polega na tym, że zastosowana funkcja sama zwraca obserwowalne, więc doskonale nadaje się do mapowania operacji asynchronicznych.

W sensie praktycznym funkcja Mapa stosuje tylko transformację w łańcuchowej odpowiedzi (nie zwracając Obserowalnej); podczas gdy funkcja FlatMap stosuje zwraca an Observable<T>, dlatego FlatMap jest zalecany, jeśli planujesz wykonać asynchroniczne wywołanie wewnątrz metody.

Podsumowanie:

  • Mapa zwraca obiekt typu T.
  • FlatMap zwraca wartość Obserable.

Jasny przykład można zobaczyć tutaj: http://blog.couchbase.com/why-couchbase-chose-rxjava-new-java-sdk .

Klient Couchbase Java 2.X korzysta z Rx, aby w wygodny sposób zapewniać połączenia asynchroniczne. Ponieważ używa Rx, ma mapę metod i FlatMap, wyjaśnienie w ich dokumentacji może być pomocne w zrozumieniu ogólnej koncepcji.

Aby obsłużyć błędy, przesłoń onError na swoim 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) { }
};

Przydałby się ten dokument: http://blog.danlew.net/2014/09/15/grokking-rxjava-part-1/

Dobre źródło zarządzania błędami za pomocą RX można znaleźć na stronie : https://gist.github.com/daschl/db9fcc9d2b932115b679

1vand1ng0
źródło
Podsumowanie jest błędne. Map i FlatMap zwracają ten sam typ, ale zastosowana funkcja zwraca inny typ.
CoXier
61

W twoim przypadku potrzebujesz mapy, ponieważ jest tylko 1 wejście i 1 wyjście.

dostarczona funkcja map po prostu przyjmuje element i zwraca element, który będzie emitowany dalej (tylko raz) w dół.

flatMap - dostarczona funkcja przyjmuje element, a następnie zwraca „Obserwowalny”, co oznacza, że ​​każdy element nowego „Obserwowalnego” będzie emitowany osobno w dalszej części.

Być może kod wyczyści dla ciebie rzeczy:

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

Wynik:

inside the map item1
item1
inside the flatMap item2
item2+
item2++
item2+++
Mt.Uulu
źródło
Nie jestem pewien, czy użycie mapy jest najlepszym pomysłem, chociaż by się udało. Załóżmy, że FileReader miał stać się wywołaniem asynchronicznym. Następnie musisz zmienić mapę na płaską mapę. Pozostawienie go jako mapy oznaczałoby, że zdarzenia nie będą uruchamiane zgodnie z oczekiwaniami i spowoduje zamieszanie. Ugryzło mnie to kilka razy, ponieważ wciąż uczę się RX Java. Uważam, że flatMap to pewny sposób na zapewnienie, że wszystko zostanie przetworzone zgodnie z oczekiwaniami.
user924272
24

Myślę o tym, że używasz, flatMapgdy funkcja, którą chcesz wstawić, map()zwraca Observable. W takim przypadku nadal możesz spróbować użyć, map()ale byłoby to niepraktyczne. Pozwól, że wyjaśnię dlaczego.

Jeśli w takim przypadku zdecydujesz się trzymać map, dostaniesz Observable<Observable<Something>>. Na przykład w twoim przypadku, gdybyśmy użyli wyimaginowanej biblioteki RxGson, która zwróciła metodę Observable<String>z jej toJson()metody (zamiast po prostu Stringzwrócić a ), wyglądałaby następująco:

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

W tym momencie byłoby dość trudne subscribe()do takiego obserwowalnego. Wewnątrz dostaniesz coś, Observable<String>do czego będziesz musiał ponownie subscribe()uzyskać wartość. Które nie jest praktyczne ani przyjemne dla oka.

Aby uczynić to użytecznym, jednym z pomysłów jest „spłaszczenie” tego, co można zaobserwować (można zacząć widzieć, skąd pochodzi nazwa _flat_Map). RxJava oferuje kilka sposobów spłaszczania obserwowalnych i dla uproszczenia załóżmy, że scalenie jest tym, czego chcemy. Scalanie zajmuje zasadniczo kilka obserwowalnych i emituje za każdym razem, gdy emituje którekolwiek z nich. (Wiele osób twierdzi, że zmiana byłaby lepszym rozwiązaniem domyślnym. Ale jeśli emitujesz tylko jedną wartość, to i tak nie ma znaczenia.)

Tak więc zmieniając nasz poprzedni fragment otrzymamy:

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

Jest to o wiele bardziej przydatne, ponieważ subskrybując to (lub mapując, filtrując lub ...) po prostu dostajesz Stringwartość. (Również pamiętać, taki wariant merge()nie istnieje w RxJava, ale jeśli rozumiesz ideę scalenia, to mam nadzieję, że rozumiesz również, jak to by działało).

Zasadniczo dlatego, że takie merge()powinny być prawdopodobnie użyteczne tylko wtedy, gdy uda się zwrócić map()obserwowalny, a więc nie trzeba wpisywać tego w kółko, flatMap()został stworzony jako skrót. Stosuje funkcję mapowania tak jak normalnie map(), ale później zamiast emitować zwracane wartości, „spłaszcza” je (lub scala).

To ogólny przypadek użycia. Jest to najbardziej przydatne w bazie kodu, która używa Rx w całym miejscu i masz wiele metod zwracających obserwowalne, które chcesz połączyć z innymi metodami zwracającymi obserwowalne.

W twoim przypadku jest to również przydatne, ponieważ map()może przekształcić tylko jedną emitowaną wartość w onNext()inną emitowaną wartość onNext(). Ale nie może przekształcić go w wiele wartości, żadnej wartości lub błędu. I jak napisał akarnokd w swojej odpowiedzi (i pamiętaj, że jest znacznie mądrzejszy ode mnie, prawdopodobnie ogólnie, ale przynajmniej jeśli chodzi o RxJava), nie powinieneś rzucać wyjątków od siebie map(). Zamiast tego możesz użyć flatMap()i

return Observable.just(value);

kiedy wszystko idzie dobrze, ale

return Observable.error(exception);

kiedy coś zawiedzie.
Zobacz jego odpowiedź na pełny fragment: https://stackoverflow.com/a/30330772/1402641

Marcin Koziński
źródło
1
to moja ulubiona odpowiedź. po prostu zagnieżdżasz obserwowalną wartość IN możliwą do zaobserwowania JEŻELI, co zwraca twoja metoda.
filthy_wizard
21

Pytanie brzmi: Kiedy używasz map vs flatMap w RxJava? . Myślę, że proste demo jest bardziej szczegółowe.

Jeśli chcesz przekonwertować element emitowany na inny typ, w twoim przypadku konwersja pliku na String, map i flatMap może działać. Ale wolę operatora map, ponieważ jest to wyraźniejsze.

Jednak w niektórych miejscach flatMapmoże wykonywać magiczną pracę, ale mapnie może. Na przykład chcę uzyskać informacje o użytkowniku, ale najpierw muszę uzyskać jego identyfikator, gdy użytkownik się loguje. Oczywiście potrzebuję dwóch żądań i są one w porządku.

Zaczynajmy.

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

Observable<UserInfo> fetchUserInfo(String userId);

Oto dwie metody, jedna do zwrócenia logowania Response, a druga do pobrania informacji o użytkowniku.

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

Jak widać, w funkcji flatMap obowiązuje najpierw identyfikator użytkownika, Responsea następnie pobieranie informacji o użytkowniku. Po zakończeniu dwóch żądań możemy wykonać nasze zadanie, takie jak aktualizacja interfejsu użytkownika lub zapisanie danych w bazie danych.

Jednak jeśli używasz map, nie możesz napisać tak dobrego kodu. Jednym słowem, flatMapmoże pomóc nam szeregować wnioski.

CoXier
źródło
18

Oto prosty kciuk zasada , że mogę używać jako pomoc mi zdecydować, kiedy użyć flatMap()ciągu map()w Rx Observable.

Gdy podejmiesz decyzję o zastosowaniu maptransformacji, napiszesz kod transformacji, aby zwrócić jakiś obiekt, prawda?

Jeśli zwracasz jako wynik końcowy transformacji:

  • nieobserwowalny obiekt, którego byś użyłmap() . I map()zawija ten obiekt w Obserowalny i emituje go.

  • Observableobiekt, a następnie byłoby użyćflatMap() . I flatMap()rozpakowuje Observable, podnosi zwrócony obiekt, otacza go swoim Observable i emituje.

Powiedzmy na przykład, że mamy metodę titleCase (String inputParam), która zwraca tytułowy obiekt Cased String parametru wejściowego. Typem zwracanym tej metody może być Stringlub Observable<String>.

  • Jeśli typ zwrotu titleCase(..)miałby być zwykły String, wówczas użyłbyśmap(s -> titleCase(s))

  • Jeśli miałby titleCase(..)to być typ zwrotu Observable<String>, to użyłbyśflatMap(s -> titleCase(s))

Nadzieja, która wyjaśnia.

karthiks
źródło
11

Chciałem tylko dodać flatMap, że tak naprawdę nie musisz używać własnego niestandardowego Observable wewnątrz funkcji i możesz polegać na standardowych metodach / operatorach:

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

Ogólnie rzecz biorąc, powinieneś unikać rzucania wyjątków (Runtime-) od metod onXXX i wywołań zwrotnych, jeśli to możliwe, mimo że umieściliśmy w RxJava jak najwięcej zabezpieczeń.

akarnokd
źródło
Ale myślę, że mapa wystarczy. Więc flatMap i mapa to nawyk, prawda?
CoXier
6

W tym scenariuszu użyj mapy, nie potrzebujesz do tego nowej Obserowalnej.

powinieneś użyć Exceptions.propagate, który jest opakowaniem, więc możesz wysłać te sprawdzone wyjątki do mechanizmu 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
        } 
    } 
});

Następnie należy obsłużyć ten błąd w subskrybencie

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

Jest na to świetny post: http://blog.danlew.net/2015/12/08/error-handling-in-rxjava/

ndori
źródło
0

W niektórych przypadkach możesz mieć łańcuch obserwowalnych, przy czym twoje obserwowalne zwrócą inne obserwowalne. rodzaj „płaskiej mapy” rozpakowuje drugi obserwowalny, który jest zakopany w pierwszym, i umożliwia bezpośredni dostęp do danych, które drugi obserwowalny wypluwa podczas subskrybowania.

Anoop Isaac
źródło
0

Flatmap mapuje obserwowalne na obserwowalne. Mapuj elementy na przedmioty.

Flatmap jest bardziej elastyczny, ale mapa jest bardziej lekka i bezpośrednia, więc zależy to od twojej skrzynki użytkownika.

Jeśli wykonujesz JAKIEKOLWIEK asynchronię (w tym przełączanie wątków), powinieneś używać Flatmap, ponieważ Map nie sprawdzi, czy konsument został usunięty (część lekkości)

skr1p7k1dd
źródło