瞭解RxJava之操作符(二)

原文鏈接:Grokking RxJava, Part 2: Operator, Operator

第一部分中我瀏覽了RxJava的基本結構,並且介紹了map操作符。我理解你仍舊沒十足的意願使用RxJava,因爲你目前只是瞭解的一點點。但是看過接下來這麼文章就會有所改變,RxJava框架的強大之處在於包含了大量的操作符。

讓我通過例子,介紹更多的操作符。

0x00 初始工作

假設有這樣一個方法,通過輸入內容獲取一個URLs列表:

// Returns a List of website URLs based on a text search
Observable<List<String>> query(String text); 

我希望構建一個強大的系統,用於查詢輸入內容並顯示查詢結果。根據上一篇文章的知識,可能寫作這樣的代碼:

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

這個作品完全不盡如人意,以至於失去了修改數據流的能力。如果我希望修改每一個URL,我不得不在Subscriber處理全部的列表。怎麼能忘記酷炫的map()操作符呢!

我加入map操作符,但是要處理的還是URLs列表,在內部還是不能擺脫for-each循環。

0x01 一線希望

有個一個方法Observable.from(),使用一組事件並且每次發射一個事件:

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循環,但是這份代碼是醜陋的,產生了多個嵌套的subscriptions ,除了醜陋和難已修改,還破壞了接下來還每一提到了RxJava的特性。

0x02 改進

救星來了,屏住呼吸:flatMap()

flatMap將一個發射數據的Observable變換爲都個Observables,然後將它們發射的數據合併後放在一個單獨的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?這裏的關鍵概念是,新的可觀察到的返回是Subscriber要看到的。Subscriber不再收到List,而是收到一系列單獨的字符串,就像Observable.from()。

這裏對應我來說,比較難以理解。但是一旦頓悟,就飛上雲霄。

0x03 還可以更好

不得不強調,flapMap()可以返回任何的Observable。

假設有下面這樣的方法:

// Returns the title of a website, or null if 404
Observable<String> getTitle(String URL);

現在我不想打印URLs列表,我希望打印一個網站的標題。但是遇到一個問題,這個方法之一次只接受一個URL,並且返回的是發射字符串的Observable,而不是字符串。

通過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組合成一個Observables。太酷了!

不止於此,請注意我組合了兩個API的鏈式調用。當然,我可以組合任意數量的API調用。想想回調地獄吧,現在的邏輯多麼簡單。

0x04 豐富的操作符

目前,我們只接觸兩個操作符,還有很多沒有接觸的。我們怎麼樣改善我們的代碼呢?

如果訪問URL遇到404,getTitle()返回null。如果我們想看到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()發出指定數量的事件。(在本例中,如果標題數量少於5個,Observable會提前停止)。

現在我們希望同時在磁盤上保存每一個結果:

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包含大量的操作符。雖然多的嚇人,但是我們值得了解每一個操作符。一旦你知道操作符的使用場景,你就能將操作符轉化爲真正的力量。

在這些操作符之外,你甚至可以編寫自己的操作符。這已經超出本文的範圍,基本上只有你想不到的,沒有做不到的。

0x05 感覺怎樣?

如果這些還不能決心使用RxJava,那你又爲什麼關心每一個操作符呢?

主要理念3:操作符可以對數據流做任何事。
唯一的限制就在於你自己

你可以通過響應式簡化複雜的邏輯。RxJava可以複雜的程序分解爲可以組合的片段,這就是響應式編程的魅力。隨着你對RxJava的瞭解,你會爲它所折服。

在第三部分,我將介紹RxJava的其他特性,如錯誤處理和併發性。
另外,想想這多麼簡化了數據最終的消費形式。在例子最後,我通過兩個API調用,修改數據並保存在磁盤。但是Subscriber並不瞭解這些,它最終只消費Observable。封裝簡化編程。

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