2014-04-03 21 views
129

Kiedy używasz map vs flatMap w RxJava?Kiedy używasz map vs flatMap w RxJava?

powiedzieć na przykład, chcemy mapować pliki zawierające JSON do łańcuchów, które zawierają JSON--

Korzystanie mapę, mamy do czynienia z wyjątkiem jakoś. 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 :(
    } 
}); 

Korzystanie flatMap, jest dużo bardziej gadatliwy, ale możemy przekazać ten problem w dół łańcucha obserwabli i obsługi błędu, jeśli zdecydujemy się gdzieś indziej, a nawet ponownie:

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 mapy, ale obsługa błędów w płaskiej mapie (a nie gadatliwości). Nie widziałem żadnych dobrych praktyk w tej dziedzinie i jestem ciekawy, jak to jest stosowane w praktyce.

Odpowiedz

88

map przekształcić jedno wydarzenie na drugie. flatMap przekształcić jedno wydarzenie na zero lub więcej zdarzeń. (jest to wzięte z IntroToRx)

Jeśli chcesz przekształcić json w obiekt, użycie mapy powinno wystarczyć.

Radzenie sobie z FileNotFoundException jest kolejnym problemem (użycie mapy lub płaskiej mapy nie rozwiązałoby tego problemu).

Aby rozwiązać problem wyjątków, po prostu wyrzuć go z wyjątkiem Non checked: RX wywoła funkcję 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 taka sama wersja z 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 
     } 
    } 
}); 

Możesz powrócić także w wersji flatMap nowy obserwowalne, że jest po prostu błąd.

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

To nie wywołuje 'subscriber.onError()' itd. Wszystkie przykłady, które widziałem, przekierowywały błędy w ten sposób. Czy to nie ma znaczenia? –

+7

Należy zauważyć, że konstruktory 'OnErrorThrowable' są' prywatne' i zamiast tego należy użyć 'OnErrorThrowable.from (e)'. –

+0

Właśnie zaktualizowałem. OnErrorThrowable.from (e) nie zachowuje wartości, więc korzystam z OnErrorThrowable.addValueAsLastCause (e, file), która powinna zachować wartość. – dwursteisen

53

FlatMap zachowuje się bardzo podobnie mapie, różnica jest taka, że ​​funkcja dotyczy zwraca obserwowalne sama, więc doskonale nadaje się do mapowania ponad operacji asynchronicznych.

W sensie praktycznym funkcja Mapa dotyczy właśnie przekształcenia nad połączoną odpowiedzią (nie zwraca Obserwowalnego); funkcja FlatMap zwraca wartość Observable<T>, dlatego FlatMap jest zalecany, jeśli planujesz wykonać wywołanie asynchroniczne wewnątrz metody.

Podsumowanie:

  • Mapa zwraca obiekt typu T
  • FlatMap zwraca widoczne.

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

Klient Couchbase Java 2.X używa Rx do wygodnego wykonywania połączeń asynchronicznych. Ponieważ używa Rx, ma mapę metod i FlatMap, wyjaśnienie w ich dokumentacji może być pomocne w zrozumieniu ogólnej koncepcji.

Aby poradzić sobie z błędami, należy zastąpić onError na 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) { } 
}; 

To może pomóc spojrzeć na ten dokument: http://blog.danlew.net/2014/09/15/grokking-rxjava-part-1/

Dobrym źródłem informacji na temat zarządzania błędy z RX można znaleźć pod adresem: https://gist.github.com/daschl/db9fcc9d2b932115b679

+0

Podsumowanie jest błędne. Mapa i FlatMap zwracają ten sam typ, ale funkcja, którą stosują, zwraca inny typ. – CoXier

38

w twoim przypadku myślę, że trzeba map, ponieważ istnieje tylko 1 wejście i 1 wyjście.

mapa - dostarczona funkcja po prostu akceptuje pozycję i zwraca element, który zostanie wysłany dalej (tylko raz) w dół.

Funkcja flatMap - provided przyjmuje pozycję, a następnie zwraca "Observable", co oznacza, że ​​każda pozycja nowego "Observable" będzie emitowana osobno w dół.

Może być kod będzie wyczyścić rzeczy dla Ciebie.

 //START DIFFERENCE BETWEEN MAP AND FLATMAP 
    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); 
    //END DIFFERENCE BETWEEN MAP AND FLATMAP 

wyjściowa:

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

Twój kod nie działa. Czy chciałeś napisać "Observable.just (" item1 ")'? –

+1

Dzięki, Henrique de Sousa –

10

Chciałem tylko dodać, że z flatMap, naprawdę nie trzeba używać swój własny Obserwowalne wewnątrz funkcji i można polegać na standardowych metodach fabryka/operatorów :

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

Generalnie należy unikać rzucania (Runtime-) wyjątki od metod onXXX callbacków i jeśli to możliwe, choć złożyliśmy tyle jako zabezpieczenia mogliśmy w RxJava.

+0

Ale myślę, że mapa wystarczy. Więc flatMap i mapa to nawyk? – CoXier

4

W tym scenariuszu używaj mapy, nie potrzebujesz dla niej nowego Obserwatorium.

należy użyć Exceptions.propagate, co jest opakowaniem, dzięki czemu można wysłać tych sprawdzonych wyjątków 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 powinien obsługiwać ten błąd w abonenta

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

Nie to doskonały post dla tego: http://blog.danlew.net/2015/12/08/error-handling-in-rxjava/

20

Sposób, w jaki myślę o tym, to użycie flatMap, gdy funkcja, którą chcesz umieścić wewnątrz map() zwraca Observable. W takim przypadku możesz nadal próbować używać map(), ale byłoby to niepraktyczne. Pozwól mi spróbować wyjaśnić, dlaczego.

Jeśli w takim przypadku zdecydujesz się trzymać z numerem map, otrzymasz numer Observable<Observable<Something>>. Na przykład w przypadku, jeśli użyliśmy wyimaginowanej biblioteki RxGson, że zwrócił Observable<String> od niego znajduje toJson() sposób (zamiast po prostu zwrócenie String) to będzie wyglądać 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ść podstępny do subscribe() do takiego obserwowalnego. Wewnątrz tego dostaniesz Observable<String>, do którego znowu będziesz potrzebował subscribe(), aby uzyskać wartość. Co nie jest praktyczne ani przyjemne do obejrzenia.

Jednym z pomysłów jest "spłaszczenie" tego obserwowalnego obserwatorium (możesz zacząć widzieć, skąd pochodzi nazwa _flat_Map). RxJava oferuje kilka sposobów na spłaszczanie obserwowalnych obiektów, a dla uproszczenia pozwala założyć, że chcemy uzyskać merge. Merge w zasadzie bierze kilka obserwowalnych i emituje, gdy tylko którykolwiek z nich emituje. (Wielu ludzi twierdzą switch byłby lepszy domyślny Ale jeśli emitując tylko jedną wartość, to i tak nie ma znaczenia.).

Więc zmieniające nasz poprzedni fragment chcemy uzyskać:

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ż subskrybowanie tego (lub mapowanie, lub filtrowanie, lub ...) po prostu uzyskać wartość String. (Również, pamiętajcie, taki wariant merge() nie istnieje w RxJava, ale jeśli zrozumieć ideę scalenia potem mam nadzieję, że również zrozumieć, jak to będzie działać.)

Więc w zasadzie, bo takie merge() prawdopodobnie powinien być tylko użyteczne, gdy powodzi się map() zwracając obserwowalne i dlatego nie trzeba wpisywać tego w kółko, flatMap() został stworzony jako skrót. Wykorzystuje funkcję odwzorowania tak, jak normalnie, ale później zamiast emitować zwrócone wartości, również "spłaszcza" je (lub je scala).

To ogólny przypadek użycia. Jest to najbardziej przydatne w kodzie źródłowym, które używa Rx allover to miejsce i masz wiele metod zwracających obserwowalne, które chcesz połączyć z innymi metodami zwracającymi obserwowalne.

W przypadku użycia okazało się również przydatne, ponieważ map() może przekształcić tylko jedną wartość emitowaną w onNext() na inną wartość emitowaną w onNext(). Ale nie może przekształcić go w wiele wartości, żadnej wartości ani błędu. I jak napisał akarnokd w swojej odpowiedzi (i uważam, że jest o wiele mądrzejszy ode mnie, prawdopodobnie w ogóle, ale przynajmniej jeśli chodzi o RxJava) nie powinieneś wyrzucać wyjątków z twojego map(). Zamiast więc można używać flatMap() i

return Observable.just(value); 

gdy wszystko idzie dobrze, ale

return Observable.error(exception); 

gdy coś się nie powiedzie.
Zobacz jego odpowiedź dla pełnego urywek: https://stackoverflow.com/a/30330772/1402641

+1

to jest moja ulubiona odpowiedź. w gruncie rzeczy kończy się zagnieżdżanie obserwowalne w obserwowalnym IF, to jest to, co zwraca twoja metoda. –

10

Oto prosty kciukiem zasada że używam pomóc mi zdecydować, jak podczas korzystania flatMap() nad map() w Rx Observable.

Gdy dojdziesz do decyzji, że zamierzasz zastosować transformację map, napiszesz kod transformacji, aby zwrócić trochę obiektu w prawo?

Jeśli wracamy jako wynik końcowy swojej transformacji jest:

  • nieobserwowalnego obiekt wtedy byłoby użyć tylko map(). I map() zawija obiekt w Obserwowalne i emituje go.Obiekt

  • obiektu flatMap(). I flatMap() unwraps the Observable, odbiera zwracany obiekt, opakowuje go z własnym obserwowalnym i emituje go.

Załóżmy na przykład, że mamy metodę titleCase (String inputParam), która zwraca obiekt Tited Cased String parametru wejściowego. Typ zwrotu tej metody może być String lub Observable<String>.

  • Jeśli typ powrót titleCase(..) miały być zwykłe String, to byłoby użyć map(s -> titleCase(s))

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

Nadzieję, że wyjaśnia.

0

W niektórych przypadkach może się zdarzyć, że będziesz mieć łańcuch obserwowalnych obiektów, w których twoje obserwowalne zwrócą kolejną obserwowalną. "flatmapowy" rodzaj rozpakowuje drugi obserwowalny, który jest pochowany w pierwszym i pozwala na bezpośredni dostęp do danych, które są obserwowalne, a które wypluwają podczas subskrybowania.

2

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

Jeśli chcesz przekonwertować element emitowany na inny typ w twoim przypadku, przekonwertuj plik na ciąg, mapę i mapę płaską mogą działać. Ale wolę operatora map, ponieważ jest jaśniejszy.

Jednak w pewnym miejscu flatMap może wykonywać magiczne prace, ale map nie może. Na przykład chcę uzyskać informacje o użytkowniku, ale muszę najpierw uzyskać jego identyfikator podczas logowania użytkownika. Oczywiście potrzebuję dwóch żądań i są one w porządku.

Zacznijmy.

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

Observable<UserInfo> fetchUserInfo(String userId); 

Oto dwa sposoby, jeden do logowania powrócił Response, a drugi do pobierania informacji o użytkowniku.

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

Jak widać, funkcja ma zastosowanie w flatMap, najpierw uzyskać identyfikator użytkownika z Response następnie pobrać informacji o użytkowniku. Po zakończeniu dwóch żądań możemy wykonać naszą pracę, na przykład zaktualizować interfejs użytkownika lub zapisać dane w bazie danych.

Jeśli jednak używasz map, nie możesz napisać tak ładnego kodu. Jednym słowem, flatMap może pomóc nam szeregować żądania.