RxJava操作符之轉換操作符(四)

LeiHolmes發表於2017-10-31

前言

  上一篇文章我們學習了建立類操作符,本篇我們將一起來學習RxJava轉換類操作符。所謂轉換,就是將事件序列中的物件或整個序列進行加工處理,轉換成不同的事件或事件序列。下面來看下轉換類操作符都有哪些及其使用場景。
  

初始化資料

  還是使用系列第一篇的小區與房源的例子。先初始化假資料以便實踐操作符時使用。

//小區實體
public class Community {
    private String communityName; //小區名稱
    private List<House> houses; //房源集合
}
//房源實體
public class House {
    private float size; //大小
    private int floor; //樓層
    private int price; //總價
    private String decoration; //裝修程度
    private String communityName; //小區名稱
}複製程式碼
private List<Community> communities;

private void initData() {
    communities = new ArrayList<>();
    List<House> houses1 = new ArrayList<>();
    for (int i = 0; i < 5; i++) {
        if (i % 2 == 0) {
            houses1.add(new House(105.6f, i, 200, "簡單裝修", "東方花園"));
        } else {
            houses1.add(new House(144.8f, i, 520, "豪華裝修", "東方花園"));
        }
    }
    communities.add(new Community("東方花園", houses1));

    List<House> houses2 = new ArrayList<>();
    for (int i = 0; i < 5; i++) {
        if (i % 2 == 0) {
            houses2.add(new House(88.6f, i, 166, "中等裝修", "馬德里春天"));
        } else {
            houses2.add(new House(123.4f, i, 321, "精緻裝修", "馬德里春天"));
        }
    }
    communities.add(new Community("馬德里春天", houses2));

    List<House> houses3 = new ArrayList<>();
    for (int i = 0; i < 5; i++) {
        if (i % 2 == 0) {
            houses3.add(new House(188.7f, i, 724, "豪華裝修", "帝豪家園"));
        } else {
            houses3.add(new House(56.4f, i, 101, "普通裝修", "帝豪家園"));
        }
    }
    communities.add(new Community("帝豪家園", houses3));
}複製程式碼

轉換操作符

Map

  map操作符,接收一個指定的Func1型別物件,然後將其應用到每一個由Observable發射的值上,進而將發射的值轉換為我們期望的值。來看一下原理圖與例項:

//將一組Integer轉換成String
Observable.just(1, 2, 3, 4, 5)
        .map(new Func1<Integer, String>() {
            @Override
            public String call(Integer integer) {
                return "This is " + integer;
            }
        })
        .subscribe(new Action1<String>() {
            @Override
            public void call(String s) {
                Log.e("rx_test", s);
            }
        });

//將Community集合轉換為每一個Community並獲取其name
Observable.from(communities)
        .map(new Func1<Community, String>() {
            @Override
            public String call(Community community) {
                return community.getCommunityName();
            }
        })
        .subscribe(new Action1<String>() {
            @Override
            public void call(String communityName) {
                Log.e("rx_test", "小區名稱為:" + communityName);
            }
        });複製程式碼

  輸出結果:

This is 1
This is 2
This is 3
This is 4
This is 5
小區名稱為:東方花園
小區名稱為:馬德里春天
小區名稱為:帝豪家園複製程式碼

  由輸出結果可看出,map操作符可用來進行資料的型別轉換,拼接或者對集合進行遍歷等1對1的轉換。第一個例子中,Func1<Integer, String>()第一個引數是發射資料當前的型別,第二個引數是轉換之後的資料型別。Action1<String>中引數也為發射資料轉換之後的資料型別。
  注意資料型別需對應準確,不要弄錯了。

FlatMap

  flatMap操作符,也是用來轉換的,但與map操作符不同之處是,flatMap()返回的是Observable物件,且這個Observable物件並不是被直接傳送到了 Subscriber的回撥方法中。
  這麼說可能不易理解,我們來看小區與房的例子,現在有3個小區,如果我們想列印出這3個小區中所有房源的資訊,通過RxJava要如何做到?按照之前學習的我們或許會這麼實現:

Observable.from(communities)
        .subscribe(new Action1<Community>() {
            @Override
            public void call(Community community) {
                for (House house : community.getHouses()) {
                    Log.e("rx_test", "flatMap:小區名稱:" + house.getCommunityName() 
                        + ",價格:" + house.getPrice() + ",樓層:" + house.getFloor());
                }
            }
        });複製程式碼

  按照這種實現方法我們只可獲取到每個小區這一層,想要獲取小區中的房源還需進行一層for迴圈遍歷,這就違背了RxJava的原則了。那麼來看下flatMap()如何實現:

Observable.from(communities)
        .flatMap(new Func1<Community, Observable<House>>() {
            @Override
            public Observable<House> call(Community community) {
                return Observable.from(community.getHouses());
            }
        })
        .subscribe(new Action1<House>() {
            @Override
            public void call(House house) {
                Log.e("rx_test", "flatMap:小區名稱:" + house.getCommunityName()
                    + ",價格:" + house.getPrice() + ",樓層:" + house.getFloor());
            }
        });複製程式碼

  這樣的程式碼是不是看起來舒心多了,再來看下flatMap()是如何實現的。
  首先from()接收到小區集合communities後為其建立了一個Observable,依次將每個小區傳遞給flatMap(),flatMap()在每次接收到小區後會將其中包含的房源集合拿出來又建立了一個房源Observable,並啟用這個房源Observable讓其開始發射事件,之後返回給小區集合的Observable,最後小區集合的Observable再將這些事件統一交給Subscriber的回撥方法去處理。
  整個過程有兩級Observable在運作,相當於將小區集合Observable這個初始物件鋪平之後再通過統一路徑分發下去,鋪平這個工作就是flatMap所做的。
  輸出結果:

flatMap:小區名稱:東方花園,價格:200,樓層:0
flatMap:小區名稱:東方花園,價格:520,樓層:1
flatMap:小區名稱:東方花園,價格:200,樓層:2
flatMap:小區名稱:東方花園,價格:520,樓層:3
flatMap:小區名稱:東方花園,價格:200,樓層:4
flatMap:小區名稱:馬德里春天,價格:166,樓層:0
flatMap:小區名稱:馬德里春天,價格:321,樓層:1
flatMap:小區名稱:馬德里春天,價格:166,樓層:2
flatMap:小區名稱:馬德里春天,價格:321,樓層:3
flatMap:小區名稱:馬德里春天,價格:166,樓層:4
flatMap:小區名稱:帝豪家園,價格:724,樓層:0
flatMap:小區名稱:帝豪家園,價格:101,樓層:1
flatMap:小區名稱:帝豪家園,價格:724,樓層:2
flatMap:小區名稱:帝豪家園,價格:101,樓層:3
flatMap:小區名稱:帝豪家園,價格:724,樓層:4複製程式碼

  由輸出結果可看出這3個小區的所有房源資訊都被依次列印了出來,但flatMap()有一個問題就是當資料量過大時可能會出現輸出資料順序交錯的問題。
  官方原理圖:

ConcatMap

  concatMap操作符,與flatMap()功能類似。不同之處是concatMap()採用連線方式而不是合併方式,所以其發射的資料是嚴格按照順序的,這就解決了flatMap()有可能發生資料交錯的問題。
  原理圖:

FlatMapIterable

  flatMapIterable操作符,也與flatMap()相似,不同之處在於flatMapIterable轉化多個Observable是使用Iterable作為源資料的。

Observable.from(communities)
        .flatMapIterable(new Func1<Community, Iterable<House>>() {
            @Override
            public Iterable<House> call(Community community) {
                return community.getHouses();
            }
        }).subscribe(new Action1<House>() {
    @Override
    public void call(House house) {
        Log.e("rx_test", "flatMap:小區名稱:" + house.getCommunityName()
                    + ",價格:" + house.getPrice() + ",樓層:" + house.getFloor());
    }
});複製程式碼

SwitchMap

  switchMap轉換操作符,也與flatMap()相似,每當源Observable發射新資料項(Observable)時,它將取消訂閱並停止監視之前那個資料項產生Observable,並開始監視當前發射的這一個。

Observable.from(communities)
        .switchMap(new Func1<Community, Observable<House>>() {
            @Override
            public Observable<House> call(Community community) {
                return Observable.from(community.getHouses());
            }
        })
        .subscribe(new Action1<House>() {
            @Override
            public void call(House house) {
                Log.e("rx_test", "flatMap:小區名稱:" + house.getCommunityName()
                    + ",價格:" + house.getPrice() + ",樓層:" + house.getFloor());
            }
        });複製程式碼

  如之前的例子,當資料量很大時,某一時刻,第一個小區所生成的小房源Observable正在發射資料,這時第二個小區所生成的小房源Observable被啟用,則第一個小區的小Observable就會被取消訂閱,其還未發射的資料也不在發射了。第二個小區小Observable開始發射資料,之後都同理。
  原理圖:

Scan

  scan操作符,對一個序列的資料應用一個函式,並將這個函式的結果發射出去作為下個資料應用函式時的第一個引數使用。

//例如:先輸出1,再將1+2=3作為下個資料發出,3+3=6再作為下個資料發出,以此類推。
Observable.just(1, 2, 3, 4, 5)
        .scan(new Func2<Integer, Integer, Integer>() {
            @Override
            public Integer call(Integer integer, Integer integer2) {
                return integer + integer2;
            }
        })
        .subscribe(new Action1<Integer>() {
            @Override
            public void call(Integer integer) {
                Log.e("rx_test", "scan:" + integer);
            }
        });複製程式碼

  輸出結果:

scan:1
scan:3
scan:6
scan:10
scan:15複製程式碼

  原理圖:

GroupBy

  groupBy操作符,將原始Observable發射的資料按照key來拆分成一些小的Observable,然後這些小的Observable分別發射其所包含的的資料。通俗的說就是按照某個欄位將資料進行分類再發射。
  來看一個例子:有幾個小區的多套房源資料,現在需要將其按照小區名稱進行分類並輸出。

List<House> houseList = new ArrayList<>();
houseList.add(new House(105.6f, 1, 200, "簡單裝修", "東方花園"));
houseList.add(new House(144.8f, 3, 300, "豪華裝修", "馬德里春天"));
houseList.add(new House(88.6f, 2, 170, "簡單裝修", "東方花園"));
houseList.add(new House(123.4f, 1, 250, "簡單裝修", "帝豪家園"));
houseList.add(new House(144.8f, 6, 350, "豪華裝修", "馬德里春天"));
houseList.add(new House(105.6f, 4, 210, "普通裝修", "東方花園"));
houseList.add(new House(188.7f, 3, 400, "精緻裝修", "帝豪家園"));
houseList.add(new House(88.6f, 2, 180, "普通裝修", "東方花園"));
//根據小區名稱進行分類
Observable<GroupedObservable<String, House>> groupByCommunityNameObservable = Observable
        .from(houseList)
        .groupBy(new Func1<House, String>() {
            @Override
            public String call(House house) {
                //提供分類規則的key
                return house.getCommunityName();
            }
        });
Observable.concat(groupByCommunityNameObservable) //concat組合操作符,將多個Observable有序組合併傳送,後期會詳細講解
        .subscribe(new Action1<House>() {
            @Override
            public void call(House house) {
                Log.e("rx_test", "groupBy:" + "小區:" + house.getCommunityName() + ",價格:" + house.getPrice());
            }
        });複製程式碼

  建立一個新的Observable:groupByCommunityNameObservable,它將會傳送一個帶有GroupedObservable的序列(也就是指傳送的資料項的型別為GroupedObservable)。GroupedObservable是一個特殊的Observable,它基於一個分組的key,在這個例子中的key就是小區名。
  輸出結果:

groupBy:小區:東方花園,價格:200
groupBy:小區:東方花園,價格:170
groupBy:小區:東方花園,價格:210
groupBy:小區:東方花園,價格:180
groupBy:小區:馬德里春天,價格:300
groupBy:小區:馬德里春天,價格:350
groupBy:小區:帝豪家園,價格:250
groupBy:小區:帝豪家園,價格:400複製程式碼

  原理圖:

總結

  到此,本篇關於RxJava的常用轉換類操作符就講解完畢了,下一篇我們將一起研究RxJava的四類操作符中的過濾操作符都有哪些以及如何使用。
  技術渣一枚,有寫的不對的地方歡迎大神們留言指正,有什麼疑惑或者建議也可以在我Github上RxJavaDemo專案Issues中提出,我會及時回覆。
  附上RxJavaDemo的地址:
  RxJavaDemo

相關文章