RxJava中級進階--map和flatmap

關於RxJava可以說的很多,但是要想了解RxJava的核心,只有從操作符去切入。

上一篇我們介紹了通用的Operator,像just/merge/filter。今天要介紹兩個我認爲是RxJava中最牛逼的Operator, map和flatmap。

在掌握map和flatmap的基礎上,就可以去了解RxJava的核心操作 lift() 。可以說RxJava的核心是lift, 研究它需要對RxJava的基本操作符有一定概念和印象,很多初學者在對操作符沒有印象的基礎上就去了解它的核心,然後就在 lift() 這個api上被勸退了。

map()

RxJava doc對 map() 的描述如下

Returns an Observable that applies a specified function to each item emitted by the source Observable and emits the results of these function applications.

具象點地翻譯過來就是, 定義一個方法用來處理異步源的每一個結果,對這些結果進行一種轉換變成新的數據,然後繼續下發給原來的observer。

看定義不如看代碼,下面是之前通過網絡獲取天氣情況的demo中的代碼,用的是 Retrofit + RxJava,

Observable.from(cities)
          .flatMap(new Func1<String, Observable<WeatherBean>>() {
              @Override
              public Observable<WeatherBean> call(String s) {
                  ApiWeather apiWeather = retrofit.create(ApiWeather.class);
                  return apiWeather.getWeather(s, AppConstant.APP_KEY);
              }
          }).subscribeOn(Schedulers.io())
          .observeOn(AndroidSchedulers.mainThread())
          .subscribe(new Observer<WeatherBean>() {
              ...
              @Override
              public void onNext(WeatherBean bean) {
                  String temp = String.valueOf(bean.getMain().getTemp()); //<-- 這裏接收到的是WeatherBean
              }
          });

註釋處的代碼,接收了一個bean,而我們需要的是bean中的 temp 值,需要手動做一次轉換。 我們覺得這樣太不軟件工程了,我想讓observer直接接收到的就是一個 temp 值怎麼辦? 用map()! 下面是用map()修改之後的代碼,注意這兩段代碼爲了簡潔都只列出了 onNext 方法。

Observable.from(cities)
        .flatMap(new Func1<String, Observable<WeatherBean>>() {
            @Override
            public Observable<WeatherBean> call(String s) {
                ApiWeather apiWeather = retrofit.create(ApiWeather.class);
                return apiWeather.getWeather(s, AppConstant.APP_KEY);
            }
        }).subscribeOn(Schedulers.io())
        .observeOn(AndroidSchedulers.mainThread())
        .map(new Func1<WeatherBean, String>() { //<-- 這裏就是上面說的定義的方法
            @Override
            public String call(WeatherBean bean) {
                return String.valueOf(bean.getMain().getTemp()); //<-- 在這裏做轉換
            }
        })
        .subscribe(new Observer<String>() {
            ...
            @Override
            public void onNext(String s) {
                Log.d(TAG, "onNext: " + s); //<-- 這裏就直接拿到 String 對象
            }
        });

上面就是用map改造後的樣子,observer接收到的就直接是String對象。 留意一下 Func1 這個方法,它只有一個接口 call,通過泛型接收參數 T 然後返回 R, 相當於在Observer接收數據之前插入了一個轉換, 放到這個例子裏就是接收WeatherBean,然後轉換爲String返回。

所謂的map(),可以理解爲一對一的變換,這也是RxJava種最基礎的變換。就像在地圖上去按位置查找一樣。 可能有同學要噴我說,這代碼不是比之前還多了幾行,更不軟件工程嗎? 也沒錯,不過可以換個角度看。按之前的邏輯是接收到bean後再轉換,那是命令式的思路。 使用map()之後的邏輯是在一個數據流上某個位置插入一個變換,讓這個流的數據以新的方式向下派發,這是響應式/鏈式調用的思路。可以感受這種思維的差異。 而且從代碼簡潔性上來說,上面的代碼在經過 lambda 簡化之後是這樣的

apiWeather.getWeather("Beijing", AppConstant.APP_KEY)
        .subscribeOn(Schedulers.io())
        .observeOn(AndroidSchedulers.mainThread())
        .map((Func1) (bean) -> {return String.valueOf(bean.getMain().getTemp());})
        .subscribe(new Observer<String>(){...});

這樣的代碼一眼就能看出在流程上每個步驟都做了什麼事。這纔是代碼的魅力所在啊。

flatmap()

flatmap()是一個很牛逼的變換,如果看完這一段你還不能體會它的牛逼之處那應該是我說的不夠好,希望能提建議讓我知道該怎麼行文才能更好的描述它。 RxJava對flatmap()的doc描述如下

Returns an Observable that emits items based on applying a function that you supply to each item emitted by the source Observable, where that function returns an Observable, and then merging those resulting Observables and emitting the results of this merger.

翻譯一下

定義一個處理原始異步源返回數據的方法 func,這個方法把返回的數據再次封裝,返回會派發多個結果的Observable。flatmap把這些 Observable 合併到一起,把他們的結果依次發送給Observer。

如果你的英文不錯的話建議看原版英文doc,我很努力的翻譯成中文還是覺得描述的不夠精確。 map跟flatmap的區別在於,map 需要定義的 func 返回的不是 Observable,而只是普通的數據。而 flatmap返回的是一個 Observable。 注意這裏說的是他們的參數 Func1 在返回值上的差異,不要搞成map和flatmap的返回值了,這倆的返回值都一樣。

public final <R> Observable<R> map(Func1<? super T, ? extends R> func) {...}
public final <R> Observable<R> flatMap(Func1<? super T, ? extends Observable<? extends R>> func) {...}

如果你已經手擼過一兩個RxJava的代碼,或者已經對它的Observable有一定的概念,看到這裏應該能明白 Func1 返回 Observable 的神奇之處。不明白也沒關係,我們用代碼說明。 這裏用上次介紹的 just() 方法從一個數組中取出各個城市的名字,然後走網絡請求獲取各個城市的氣溫,

String[] cities = new String[]{"Beijing", "Shanghai", "Guangzhou"};
Observable.from(cities)
          .flatMap(new Func1<String, Observable<WeatherBean>>() {
              @Override
              public Observable<WeatherBean> call(String s) {
                  ApiWeather apiWeather = retrofit.create(ApiWeather.class);
                  return apiWeather.getWeather(s, AppConstant.APP_KEY);
              }
          }).subscribeOn(Schedulers.io())
          .observeOn(AndroidSchedulers.mainThread())
          .subscribe(new Observer<WeatherBean>() {
              ...
              @Override
              public void onNext(WeatherBean bean) {
                  String temp = String.valueOf(bean.getMain().getTemp());
              }
          });

這裏需要點想象力,把 cities 想象成一個異步源。這個異步源會返回多個String作爲異步結果,flatmap 依據這些異步結果進一步做異步操作,再將最後的結果派發給 observer。 flatmap 表達的就是這種意思,某個異步源會派發多個數據,flatmap接受他們並進一步產生更多的數據,最後派發給observer,這是一個“鋪平”的過程。回過頭去看flatmap的英文定義相信你會有新的理解。

flatmap的這個特性也就帶來了很牛逼的操作--異步嵌套。 比方說我們需要從服務器拿到一串城市的array,然後再去取每個城市的溫度,最後輸出。這種需求用普通的AsyncTask或者Handler來處理簡直是災難,但是對於RxJava來說不過是幾行代碼的事。 而異步嵌套甚至可以做好幾層,每一層的輸入是上一層的結果,你會發現在這種時候鏈式調用把開發效率直接提升了幾個數量級。

flatmap() 和 map()

花這麼多篇幅講這兩個方法是因爲它們是進一步瞭解RxJava的切入口,看 flatmap() 的源碼就明白了,

public final <R> Observable<R> flatMap(Func1<? super T, ? extends Observable<? extends R>> func) {
    ...
    return merge(map(func));
}

public static <T> Observable<T> merge(Observable<? extends Observable<? extends T>> source) {
    ...
    return source.lift(OperatorMerge.<T>instance(false));
}

眼熟嗎,flatmap是基於 map 和 merge的,而 merge 本身又用 lift 去實現。 這就是爲什麼我們要先熟悉 just/from/merge,map/flatmap,這些方法,再去了解 lift 的原因。

實際上在開發中掌握到 flatmap就能覆蓋80%的業務需求了。而如果你想了解RxJava的高階用法,比如自定義操作符,就不得不去了解 lift。 看到這裏如果你還沒被勸退,那麼恭喜你已經掌握了RxJava,起碼已經入門了。如果你感覺看不懂,歡迎留言不懂的地方,我盡力解釋。 後面我們會分析lift和自定義操作符,希望能幫你打開新世界的大門。 源碼可以後臺回覆"操作符"獲取。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章