在本系列文章的第一部分我講述了RxJava的基本結(jié)構(gòu),也向你介紹了map()操作符。但是,我能夠理解你還并不能夠使用它——你所掌握的還沒(méi)有到能夠使用它的程度。這種狀況即將改變——RxJava大部分的能力都來(lái)自于這個(gè)框架中的操作符。
舉個(gè)例子來(lái)介紹它的操作符吧。
栗子
假定我現(xiàn)在有這樣一個(gè)方法:
// Returns a List of website URLs based on a text search
Observable<List<String>> query(String text);
我想開(kāi)發(fā)一個(gè)能夠搜索文本并展示結(jié)果的系統(tǒng),根據(jù)上一篇文章我們所學(xué)到的,可以寫(xiě)出如下代碼:
query("Hello, world!")
.subscribe(urls -> {
for (String url : urls) {
System.out.println(url);
}
});
這個(gè)答案自然恨不能讓人滿足,因?yàn)槲揖痛藛适Я俗儞Q數(shù)據(jù)流的能力。如果我想修改每一個(gè) url,我就必須在Subscriber中完成這一切。我們也就此拋棄了使用map()的技巧。
希望之光
有一個(gè)Observable.from()的方法,能夠?qū)魅氲募喜鸱殖删唧w的對(duì)象依次發(fā)送出來(lái):
Observable.from("url1", "url2", "url3")
.subscribe(url -> System.out.println(url));
看起來(lái)像是有用的,那就看看發(fā)生了什么吧。
query("Hello, world!")
.subscribe(urls -> {
Observable.from(urls)
.subscribe(url -> System.out.println(url));
});
我拿到了一個(gè)foreach循環(huán),可結(jié)果代碼卻是一團(tuán)糟。我現(xiàn)在到手的是多重的嵌入的訂閱,除了丑陋、難以修改外,它還破壞了RxJava的一些重要的(盡管我們還不知曉)特征。我去!
更好的方法
屏氣凝神迎接你的救星吧:flatMap()。
Observable.flatMap()傳入一個(gè)Observable發(fā)送的數(shù)據(jù),返回另一個(gè)Observable發(fā)送的數(shù)據(jù)。突如其來(lái)的變化:你以為你得到的是一個(gè)數(shù)據(jù)流其實(shí)卻是另外一個(gè)。看看它是怎么解決這個(gè)問(wèn)題的:
query("Hello, world!")
.flatMap(new Func1<List<String>, Observable<String>>() {
@Override
public Observable<String> call(List<String> urls) {
return Observable.from(urls);
}
})
.subscribe(url -> System.out.println(url));
上面是完整的函數(shù),這樣你就能確切地知道到底發(fā)送了什么,但如果用lambdas來(lái)簡(jiǎn)化代碼的話,看起來(lái)就很清爽了:
query("Hello, world!")
.flatMap(urls -> Observable.from(urls))
.subscribe(url -> System.out.println(url));
flatMap()有點(diǎn)奇怪吧?它為什么會(huì)返回另外一個(gè)Observable呢?這里的關(guān)鍵點(diǎn)是Subscriber看到的就是返回的新的Observable。它并沒(méi)有收到一個(gè)List<String>——它收到的是Observable.from()返回的一系列單獨(dú)的String。
還可以更好
我必須著重強(qiáng)調(diào):flatMap()能夠返回任意它想返回的Observable。
假定現(xiàn)在我又有一個(gè)方法:
// Returns the title of a website, or null if 404
Observable<String> getTitle(String URL);
現(xiàn)在我想打印得到的每一個(gè)網(wǎng)站的標(biāo)題而不再是URL,但有幾個(gè)問(wèn)題:我的方法同時(shí)只能作用在單獨(dú)的URL上,并且它并不返回一個(gè)String,而是返回一個(gè)發(fā)送著String的Observable。
使用flatMap()來(lái)解決這個(gè)問(wèn)題其實(shí)很簡(jiǎn)單,將URL列表分割完成之后,在URL到達(dá)Subscriber之前,我可以在flatMap()中對(duì)每個(gè)URL使用getTitle():
query("Hello, world!")
.flatMap(urls -> Observable.from(urls))
.flatMap(new Func1<String, Observable<String>>() {
@Override
public Observable<String> call(String url) {
return getTitle(url);
}
})
.subscribe(title -> System.out.println(title));
再一次使用lambdas簡(jiǎn)化代碼:
query("Hello, world!")
.flatMap(urls -> Observable.from(urls))
.flatMap(url -> getTitle(url))
.subscribe(title -> System.out.println(title));
牛吧?我將多個(gè)單獨(dú)返回Observable的方法組合在了一起。爽!
還不止如此,注意我是怎么把兩個(gè)API調(diào)用合并到一個(gè)鏈?zhǔn)秸{(diào)用中的。這可以擴(kuò)展到任意多個(gè)API調(diào)用。你知道保證API調(diào)用同步有多痛苦嗎?但我們已經(jīng)跳過(guò)了回調(diào)的地獄,所有這些的相同邏輯都被封裝在了這個(gè)簡(jiǎn)短的reactive調(diào)用當(dāng)中。
豐富的操作符
到現(xiàn)在為止我們也就學(xué)了兩個(gè)操作符,但其實(shí)它還有很多。那么,我們還能怎么優(yōu)化我們的代碼呢?
getTitle()將返回null如果URL遭遇404錯(cuò)誤。但我們不想輸出“null”,事實(shí)證明我們可以將它過(guò)濾掉:
query("Hello, world!")
.flatMap(urls -> Observable.from(urls))
.flatMap(url -> getTitle(url))
.filter(title -> title != null)
.subscribe(title -> System.out.println(title));
filter()原樣發(fā)送收到的通過(guò)boolean檢查的數(shù)據(jù)。
現(xiàn)在我們一次最多只想顯示5條數(shù)據(jù):
query("Hello, world!")
.flatMap(urls -> Observable.from(urls))
.flatMap(url -> getTitle(url))
.filter(title -> title != null)
.take(5)
.subscribe(title -> System.out.println(title));
take()一次最多發(fā)送指定的數(shù)據(jù)數(shù)目(如果少于5條就提前結(jié)束)。
現(xiàn)在,我們希望將每個(gè)標(biāo)題存盤:
query("Hello, world!")
.flatMap(urls -> Observable.from(urls))
.flatMap(url -> getTitle(url))
.filter(title -> title != null)
.take(5)
.doOnNext(title -> saveTitle(title))
.subscribe(title -> System.out.println(title));
doOnNext()允許我們?cè)跀?shù)據(jù)被發(fā)送之前添加額外的行為,這個(gè)例子中就是將標(biāo)題存盤。
瞧瞧操作數(shù)據(jù)流有多么簡(jiǎn)單吧。你可以給你的程序添加更多技巧但不會(huì)引起混亂。
RxJava有著豐富的操作符,這么多的操作符的確有些嚇人但值得復(fù)習(xí)一下,這樣你就知道哪些有用。消化這些操作符需要一些時(shí)間,但是一旦你這么做了,你手上就有了洪荒之力。
根據(jù)上面提供的那些,你甚至還可以自定義自己的操作符。那超出了本文的范圍,但只要你想做,你一定可以做到的。
然后呢?
好吧,看來(lái)你是一個(gè)很難滿足的懷疑論者。為什么你應(yīng)該關(guān)注所有這些操作符?
關(guān)鍵概念 #3: 操作符能夠讓你對(duì)數(shù)據(jù)流做任何事!
你能夠只使用簡(jiǎn)單的操作符鏈就建立起復(fù)雜的邏輯。它能夠?qū)⒛愕拇a分解成可組合的片段。那就是功能性的reactive編程。你使用得越多,你思考程序的方式改變得就會(huì)越多。
另外,試想一下,要去消費(fèi)我們變換完成后的數(shù)據(jù)是多么的簡(jiǎn)單。在這個(gè)例子的最后我們使用了兩個(gè)API調(diào)用,操作了數(shù)據(jù)然后存盤,但我們的Subscriber卻并不知道那些,它只會(huì)認(rèn)為它消費(fèi)了一個(gè)簡(jiǎn)單的Observable<String>。封裝,使得編程更為簡(jiǎn)易!
在本文的第三部分,我們將學(xué)到RxJava中不直接涉及操作數(shù)據(jù)的一些其他特征。比如錯(cuò)誤處理和并發(fā)。