天天看點

深入淺出RxJava(二:操作符)

在 第一篇blog中,我介紹了RxJava的一些基礎知識,同時也介紹了map()操作符。當然如果你并沒有意願去使用RxJava我一點都不詫異,畢竟才接觸了這麼點。看完這篇blog,我相信你肯定想立即在你的項目中使用RxJava了,這篇blog将介紹許多RxJava中的操作符,RxJava的強大性就來自于它所定義的操作符。

首先先看一個例子:

準備工作

假設我有這樣一個方法:

這個方法根據輸入的字元串傳回一個網站的url清單(啊哈,搜尋引擎)

Observable<List<String>> query(String text); 
           

現在我希望建構一個健壯系統,它可以查詢字元串并且顯示結果。根據上一篇blog的内容,我們可能會寫出下面的代碼:

query("Hello, world!")
    .subscribe(urls -> {
        for (String url : urls) {
            System.out.println(url);
        }
    });
           

這種代碼當然是不能容忍的,因為上面的代碼使我們喪失了變化資料流的能力。一旦我們想要更改每一個URL,隻能在Subscriber中來做。我們竟然沒有使用如此酷的map()操作符!!!

當然,我可以使用map操作符,map的輸入是urls清單,處理的時候還是要for each周遊,一樣很蛋疼。

萬幸,還有Observable.from()方法,它接收一個集合作為輸入,然後每次輸出一個元素給subscriber:

Observable.from("url1", "url2", "url3")
    .subscribe(url -> System.out.println(url));
           

我們來把這個方法使用到剛才的場景:

query("Hello, world!")
    .subscribe(urls -> {
        Observable.from(urls)
            .subscribe(url -> System.out.println(url));
    });
           

雖然去掉了for each循環,但是代碼依然看起來很亂。多個嵌套的subscription不僅看起來很醜,難以修改,更嚴重的是它會破壞某些我們現在還沒有講到的RxJava的特性。

改進

救星來了,他就是flatMap()。

Observable.flatMap()接收一個Observable的輸出作為輸入,同時輸出另外一個Observable。直接看代碼:

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

這裡我貼出了整個的函數代碼,以友善你了解發生了什麼,使用lambda可以大大簡化代碼長度:

query("Hello, world!")
    .flatMap(urls -> Observable.from(urls))
    .subscribe(url -> System.out.println(url));
           

flatMap()是不是看起來很奇怪?為什麼它要傳回另外一個Observable呢?了解flatMap的關鍵點在于,flatMap輸出的新的Observable正是我們在Subscriber想要接收的。現在Subscriber不再收到List<String>,而是收到一些列單個的字元串,就像Observable.from()的輸出一樣。

這部分也是我當初學RxJava的時候最難了解的部分,一旦我突然領悟了,RxJava的很多疑問也就一并解決了。

還可以更好

flatMap()實在不能更贊了,它可以傳回任何它想傳回的Observable對象。

比如下面的方法:

// 傳回網站的标題,如果404了就傳回null
Observable<String> getTitle(String URL);
           

接着前面的例子,現在我不想列印URL了,而是要列印收到的每個網站的标題。問題來了,我的方法每次隻能傳入一個URL,并且傳回值不是一個String,而是一個輸出String的Observabl對象。使用flatMap()可以簡單的解決這個問題。

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

使用lambda:

query("Hello, world!")
    .flatMap(urls -> Observable.from(urls))
    .flatMap(url -> getTitle(url))
    .subscribe(title -> System.out.println(title));
           

是不是感覺很不可思議?我竟然能将多個獨立的傳回Observable對象的方法組合在一起!帥呆了!

不止這些,我還将兩個API的調用組合到一個鍊式調用中了。我們可以将任意多個API調用連結起來。大家應該都應該知道同步所有的API調用,然後将所有API調用的回調結果組合成需要展示的資料是一件多麼蛋疼的事情。這裡我們成功的避免了callback hell(多層嵌套的回調,導緻代碼難以閱讀維護)。現在所有的邏輯都包裝成了這種簡單的響應式調用。

豐富的操作符

目前為止,我們已經接觸了兩個操作符,RxJava中還有更多的操作符,那麼我們如何使用其他的操作符來改進我們的代碼呢?

getTitle()傳回null如果url不存在。我們不想輸出"null",那麼我們可以從傳回的title清單中過濾掉null值!

query("Hello, world!")
    .flatMap(urls -> Observable.from(urls))
    .flatMap(url -> getTitle(url))
    .filter(title -> title != null)
    .subscribe(title -> System.out.println(title));
           

filter()輸出和輸入相同的元素,并且會過濾掉那些不滿足檢查條件的。

如果我們隻想要最多5個結果:

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()輸出最多指定數量的結果。

如果我們想在列印之前,把每個标題儲存到磁盤:

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()允許我們在每次輸出一個元素之前做一些額外的事情,比如這裡的儲存标題。

看到這裡操作資料流是多麼簡單了麼。你可以添加任意多的操作,并且不會搞亂你的代碼。

RxJava包含了大量的操作符。操作符的數量是有點吓人,但是很值得你去挨個看一下,這樣你可以知道有哪些操作符可以使用。弄懂這些操作符可能會花一些時間,但是一旦弄懂了,你就完全掌握了RxJava的威力。

你甚至可以編寫自定義的操作符!這篇blog不打算将自定義操作符,如果你想的話,清自行Google吧。

感覺如何?

好吧,你是一個懷疑主義者,并且還很難被說服,那為什麼你要關心這些操作符呢?

因為操作符可以讓你對資料流做任何操作。

将一系列的操作符連結起來就可以完成複雜的邏輯。代碼被分解成一系列可以組合的片段。這就是響應式函數程式設計的魅力。用的越多,就會越多的改變你的程式設計思維。

另外,RxJava也使我們處理資料的方式變得更簡單。在最後一個例子裡,我們調用了兩個API,對API傳回的資料進行了處理,然後儲存到磁盤。但是我們的Subscriber并不知道這些,它隻是認為自己在接收一個Observable<String>對象。良好的封裝性也帶來了編碼的便利!

在第三部分中,我将介紹RxJava的另外一些很酷的特性,比如錯誤處理和并發,這些特性并不會直接用來處理資料。

原文連結