- 原文出自《RxJava Essentials》
- 原文作者 : Ivan Morgillo
- 譯文出自 : 開發技術前線 www.devtf.cn
- 轉載聲明: 本譯文已授權開發者頭條享有獨家轉載權,未經允許,不得轉載!
- 譯者 : yuxingxin
- 項目地址 : RxJava-Essentials-CN
在上一章中,我們探索了RxJava通用過濾方法。我們學習了如何使用filter()
方法過濾我們不需要的值,如何使用take()
得到發射元素的子集,如何使用distinct()
函數來去除重復的。我們學習了如何使用timeout()
,sample()
,以及debounce()
來利用時間。
這一章中,我們將學習如何變換可觀測序列來創建一個更好滿足我們需求的序列。
*map家族
RxJava提供了幾個mapping函數:map()
,flatMap()
,concatMap()
,flatMapIterable()
以及switchMap()
.所有這些函數都作用於一個可觀測序列,然后變換它發射的值,最后用一種新的形式返回它們。讓我們用“真實世界”合適的例子一個個的學習下。
Map
RxJava的map
函數接收一個指定的Func
對象然后將它應用到每一個由Observable發射的值上。下圖展示了如何將一個乘法函數應用到每個發出的值上以此創建一個新的Observable來發射轉換的數據。
考慮我們已安裝的應用列表。我們怎么才能夠顯示同樣的列表,但是所有的名字都是小寫。
我們的loadList()
函數可以改成這樣:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
|
private void loadList(List<AppInfo> apps) {
mRecyclerView.setVisibility(View.VISIBLE);
Observable.from(apps)
.map(new Func1<AppInfo,AppInfo>(){
@Override
public Appinfo call(AppInfo appInfo){
String currentName = appInfo.getName();
String lowerCaseName = currentName.toLowerCase();
appInfo.setName(lowerCaseName);
return appInfo;
}
})
.subscribe(new Observable<AppInfo>() {
@Override
public void onCompleted() {
mSwipeRefreshLayout.setRefreshing(false);
}
@Override
public void onError(Throwable e) {
Toast.makeText(getActivity(), "Something went wrong!", Toast.LENGTH_SHORT).show();
mSwipeRefreshLayout.setRefreshing(false);
}
@Override
public void onNext(AppInfo appInfo) {
mAddedApps.add(appInfo);
mAdapter.addApplication(mAddedApps.size() - 1,appInfo);
}
});
}
|
正如你看到的,像往常一樣創建我們發射的Observable,我們加一個map
調用,我們可以創建一個簡單的函數來更新AppInfo
對象並提供一個名字小寫的新版本給觀察者。
FlatMap
在復雜的場景中,我們有一個這樣的Observable:它發射一個數據序列,這些數據本身也可以發射Observable。RxJava的flatMap()
函數提供一種鋪平序列的方式,然后合並這些Observables發射的數據,最后將合並后的結果作為最終的Observable。
當我們在處理可能有大量的Observables時,重要是記住任何一個Observables發生錯誤的情況,flatMap()
函數將會觸發它自己的onError()
函數並放棄整個鏈。
重要的一點是關於合並部分:它允許交叉。正如上圖所示,這意味着flatMap()
函數在最后的Observable中不能夠保證源Observables確切的發射順序。
ConcatMap
RxJava的concatMap()
函數解決了flatMap()
的交叉問題,提供了一種能夠把發射的值連續在一起的鋪平函數,而不是合並它們,如下圖所示:
FlatMapIterable
作為*map家族的一員,flatMapInterable()
和flatMap()
很像。僅有的本質不同是它將源數據兩兩結成對,然后生成Iterable而不是原始數據和生成的Observables。
SwitchMap
如下圖所示,switchMap()
和flatMap()
很像,除了一點:當原始Observable發射一個新的數據(Observable)時,它將取消訂閱並停止監視之前那個數據的Observable產生的Observable,並開始監視當前這一個。
Scan
RxJava的scan()
函數可以看做是一個累加器函數。scan()
函數對原始Observable發射的每一項數據都應用一個函數,它將函數的結果填充回可觀測序列,等待和下一次發射的數據一起使用。
作為一個通用的例子,給出一個累加器:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
Observable.just(1,2,3,4,5)
.scan((sum,item) -> sum + item)
.subscribe(new Subscriber<Integer>() {
@Override
public void onCompleted() {
Log.d("RXJAVA", "Sequence completed.");
}
@Override
public void onError(Throwable e) {
Log.e("RXJAVA", "Something went south!");
}
@Override
public void onNext(Integer item) {
Log.d("RXJAVA", "item is: " + item);
}
});
|
我們得到的結果是:
1
2
3
4
5
6
7
|
RXJAVA: item is: 1
RXJAVA: item is: 3
RXJAVA: item is: 6
RXJAVA: item is: 10
RXJAVA: item is: 15
RXJAVA: Sequence completed.
|
我們也可以創建一個新版本的loadList()
函數用來比較每個安裝應用的名字從而創建一個名字長度遞增的列表。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
|
private void loadList(List<AppInfo> apps) {
mRecyclerView.setVisibility(View.VISIBLE);
Observable.from(apps)
.scan((appInfo,appInfo2) -> {
if(appInfo.getName().length > appInfo2.getName().length()){
return appInfo;
} else {
return appInfo2;
}
})
.distinct()
.subscribe(new Observable<AppInfo>() {
@Override
public void onCompleted() {
mSwipeRefreshLayout.setRefreshing(false);
}
@Override
public void onError(Throwable e) {
Toast.makeText(getActivity(), "Something went wrong!", Toast.LENGTH_SHORT).show();
mSwipeRefreshLayout.setRefreshing(false);
}
@Override
public void onNext(AppInfo appInfo) {
mAddedApps.add(appInfo);
mAdapter.addApplication(mAddedApps.size() - 1,appInfo);
}
});
}
|
結果如下:
有一個scan()
函數的變體,它用初始值作為第一個發射的值,方法特征就像:scan(R,Func2)
,就像下圖中的例子這樣:
GroupBy
拿第一個例子開始,我們安裝的應用程序列表按照字母表的順序排序。然而,如果現在我們想按照最近更新日期來排序我們的App時該怎么辦?RxJava提供了一個有用的函數從列表中按照指定的規則:groupBy()
來分組元素。下圖中的例子展示了groupBy()
如何將發射的值根據他們的形狀來進行分組。
這個函數將源Observable變換成一個發射Observables的新的Observable。它們中的每一個新的Observable都發射一組指定的數據。
為了創建一個分組了的已安裝應用列表,我們在loadList()
函數中引入了一個新的元素:
1
2
3
4
5
6
7
8
9
|
Observable<GroupedObservable<String,AppInfo>> groupedItems = Observable.from(apps)
.groupBy(new Func1<AppInfo,String>(){
@Override
public String call(AppInfo appInfo){
SimpleDateFormat formatter = new SimpleDateFormat("MM/yyyy");
return formatter.format(new Date(appInfo.getLastUpdateTime()));
}
});
|
現在我們創建了一個新的Observable,groupedItems
,將會發射一個帶有GroupedObservable
的序列。GroupedObservable
是一個特殊的Observable,它源自一個分組的key。在這個例子中,key就是String
,代表的意思是Month/Year
格式化的最近更新日期。
這一點,我們已經創建了幾個發射AppInfo
數據的Observable,用來填充我們的列表。我們想保留字母排序和分組排序。我們將創建一個新的Observable將所有的聯系起來,像通常一樣然后訂閱它:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
Observable.concat(groupedItems)
.subscribe(new Observable<AppInfo>() {
@Override
public void onCompleted() {
mSwipeRefreshLayout.setRefreshing(false);
}
@Override
public void onError(Throwable e) {
Toast.makeText(getActivity(), "Something went wrong!", Toast.LENGTH_SHORT).show();
mSwipeRefreshLayout.setRefreshing(false);
}
@Override
public void onNext(AppInfo appInfo) {
mAddedApps.add(appInfo);
mAdapter.addApplication(mAddedApps.size() - 1,appInfo);
}
});
|
我們的loadList()
函數完成了,結果是:
Buffer
RxJava中的buffer()
函數將源Observable變換一個新的Observable,這個新的Observable每次發射一組列表值而不是一個一個發射。
上圖中展示了buffer()
如何將count
作為一個參數來指定有多少數據項被包在發射的列表中。實際上,buffer()
函數有幾種變體。其中有一個時允許你指定一個skip
值:此后每當收到skip項數據,用count項數據就填充緩存。如下圖所示:
buffer()
帶一個timespan
的參數,會創建一個每隔timespan時間段就會發射一個列表的Observable。
Window
RxJava的window()
函數和buffer()
很像,但是它發射的時Observable而不是列表。下圖展示了window()
如何緩存3個數據項並把它們作為一個新的Observable發射出去。
這些Observables中的每一個都發射原始Observable數據的一個子集,數量由count
指定,最后發射一個onCompleted()
結束。正如buffer()
一樣,window()
也有一個skip
變體,如下圖所示:
Cast
RxJava的cast()
函數是本章中最后一個操作符。它是map()
操作符的特殊版本。它將源Observable中的每一項數據都轉換為新的類型,把它變成了不同的Class
。
總結
這一章中,我們學習了RxJava時如何控制和轉換可觀測序列。用我們現在所學的知識,我們可以創建、過濾、轉換我們所想要的任何種類的可觀測序列。
下一章,我們將學習如何組合Observable,合並它們,連接它們,再或者打包它們。