第4章 組合的Observe

第4章 組合的Observe

導讀

我們涵蓋了許多抑制,轉換,減少和集合的運算符。 這些運算符可以做很多工作,但是如何將多個可觀察對象組合在一起並合併爲一個呢? 如果
我們想使用ReactiveX來完成更多工作,我們需要獲取多個數據和事件流, 讓他們一起工作,並有運算符和工廠來實現這一目標。 這些結合
操作員和工廠還可以安全地處理髮生在不同線程上的Observable(在第6章中討論,併發和並行化)。

這是我們開始從使RxJava有用到使其強大的過渡。 我們將介紹 以下操作組合可觀察對象:
合併
級聯
雙關
壓縮
結合最新
分組

一、合併

在ReactiveX中完成的一項常見任務是將兩個或多個Observable 實例合併到一個實例中Observable。 合併的Observable 將同時訂閱其所有合併的源,從而使其
對於合併有限和無限的Observable有效。 有幾種方法可以利用這一點使用工廠和運算符合並行爲。

1.1、Observable.merge() 和 mergeWith()

Observable.merge()運算符將採用兩個或多個發出相同類型T的Observable 源,然後將它們合併爲一個Observable 。
如果我們只有兩到四個Observable 源要合併,則可以將每個源作爲參數傳遞給Observable.merge()工廠。 在以下代碼片段中,我將兩個Observable 實例合併到
一個Observable :

import io.reactivex.Observable;

public class Ch4_1 {
    public static void main(String[] args) {
        Observable<String> source1 =
                Observable.just("Alpha", "Beta", "Gamma", "Delta",
                        "Epsilon");
        Observable<String> source2 =
                Observable.just("Zeta", "Eta", "Theta");
        Observable.merge(source1, source2)
                .subscribe(i -> System.out.println("RECEIVED: " + i));
    }
}

輸出結果如下:

RECEIVED: Alpha

RECEIVED: Beta

RECEIVED: Gamma

RECEIVED: Delta

RECEIVED: Epsilon

RECEIVED: Zeta

RECEIVED: Eta

RECEIVED: Theta

另外,您可以使用mergeWith(),它是Observable.merge()的運算符版本:

source1.mergeWith(source2)
	   .subscribe(i -> System.out.println("RECEIVED: " + i));

Observable.merge()工廠和mergeWith()運算符將訂閱所有指定的源同時進行,但如果它們很冷且在同一根螺紋上,則可能會按順序發射排放物。 這是
只是一個實現細節,如果您明確想要觸發元素,則應該使用Observable.concat()的每個可觀察序列,並使其排放保持順序

即使使用合併工廠和運算符,也不應依賴於排序訂購似乎被保留了。 話雖如此,但每個排放的順序
來源保持可觀察。 合併源的方式是一種實現詳細信息,因此,如果要保證順序,請使用串聯工廠和運算符。

如果您有四個以上的Observable 源,則可以使用Observable.mergeArray()傳遞以下變量:
您想要合併的Observable []實例,如以下代碼片段所示。 從RxJava 2.0開始是爲JDK 6+編寫的,並且無法訪問@SafeVarargs批註,您可能會獲得某種類型的安全性
警告:

import io.reactivex.Observable;

public class Ch4_2 {
    public static void main(String[] args) {
        Observable<String> source1 =
                Observable.just("Alpha", "Beta");
        Observable<String> source2 =
                Observable.just("Gamma", "Delta");
        Observable<String> source3 =
                Observable.just("Epsilon", "Zeta");
        Observable<String> source4 =
                Observable.just("Eta", "Theta");
        Observable<String> source5 =
                Observable.just("Iota", "Kappa");
        Observable.mergeArray(source1, source2, source3, source4,
                source5)
                .subscribe(i -> System.out.println("RECEIVED: " + i));
    }
}

輸出結果如下:

RECEIVED: Alpha

RECEIVED: Beta

RECEIVED: Gamma

RECEIVED: Delta

RECEIVED: Epsilon

RECEIVED: Zeta

RECEIVED: Eta

RECEIVED: Theta

RECEIVED: Iota

RECEIVED: Kappa

您也可以將Iterable <Observable >傳遞給Observable.merge()。 它將合併所有Observable 實例
在那可迭代的。 通過放置所有這些源,我可以以類型安全的方式實現上述示例
在List <Observable >中,並將它們傳遞給Observable.merge():

import io.reactivex.Observable;

import java.util.Arrays;
import java.util.List;

public class Ch4_3 {
    public static void main(String[] args) {
        Observable<String> source1 =
                Observable.just("Alpha", "Beta");
        Observable<String> source2 =
                Observable.just("Gamma", "Delta");
        Observable<String> source3 =
                Observable.just("Epsilon", "Zeta");
        Observable<String> source4 =
                Observable.just("Eta", "Theta");
        Observable<String> source5 =
                Observable.just("Iota", "Kappa");
        List<Observable<String>> sources =
                Arrays.asList(source1, source2, source3, source4,
                        source5);
        Observable.merge(sources)
                .subscribe(i -> System.out.println("RECEIVED: " + i));
    }
}

mergeArray()獲得自己的方法而不是merge()重載的原因是爲了避免Java 8編譯器的歧義及其對函數類型的處理。 這是真的
對於所有xxxArray()運算符。

Observable.merge()與無限的Observables一起使用。 由於它將訂閱所有可觀察物和火災只要它們的排放可用,您就可以將多個無限源合併爲一個流。
在這裏,我們合併了兩個Observable.interval()源,它們以一秒和300毫秒的間隔發射,分別。 但是在合併之前,我們對發出的索引進行一些數學運算以弄清楚多少時間
已經過去並以字符串形式的源名稱發出它。 我們讓此過程運行三秒鐘:

import io.reactivex.Observable;

import java.util.concurrent.TimeUnit;

public class Ch4_4 {
    public static void main(String[] args) {
//emit every second
        Observable<String> source1 = Observable.interval(1,
                TimeUnit.SECONDS)
                .map(l -> l + 1) // emit elapsed seconds
                .map(l -> "Source1: " + l + " seconds");
//emit every 300 milliseconds
        Observable<String> source2 =
                Observable.interval(300, TimeUnit.MILLISECONDS)
                        .map(l -> (l + 1) * 300) // emit elapsed milliseconds
                        .map(l -> "Source2: " + l + " milliseconds");
//merge and subscribe
        Observable.merge(source1, source2)
                .subscribe(System.out::println);
//keep alive for 3 seconds
        sleep(3000);
    }

    public static void sleep(long millis) {
        try {
            Thread.sleep(millis);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

輸出結果如下:
Source2: 300 milliseconds

Source2: 600 milliseconds

Source2: 900 milliseconds

Source1: 1 seconds

Source2: 1200 milliseconds

Source2: 1500 milliseconds

Source2: 1800 milliseconds

Source1: 2 seconds

Source2: 2100 milliseconds

Source2: 2400 milliseconds

Source2: 2700 milliseconds

Source1: 3 seconds

Source2: 3000 milliseconds

概括而言,Observable.merge()將合併多個發出相同類型T的Observable 源,並且合併爲單個Observable 。 它適用於無限的Observables,並不一定保證
排放以任何順序出現。 如果您在乎是否嚴格要求排放,請確保每個Observable源按順序觸發,您可能需要使用Observable.concat(),我們將不久介紹。

1.2、flatMap()

RxJava中最強大和關鍵的運算符之一是flatMap()。 如果您必須花時間在瞭解任何RxJava運算符,這就是一個。 它是執行動態操作的運算符
通過獲取每個發射並將其映射到Observable來觀察Observable.merge()。 然後,合併排放從產生的Observables到單個流。

flatMap()的最簡單應用是將一個排放映射到許多排放。 說,我們要發出
來自Observable 的每個字符串中的字符。 我們可以使用flatMap()指定一個
將每個字符串映射到Observable 的Function <T,Observable > lambda函數,它將發出字母。
注意,映射的Observable 可以發射任何類型的R,與源T的發射不同。 在這
例如,它恰好是String,就像源代碼一樣:

import io.reactivex.Observable;

public class Ch4_5 {
    public static void main(String[] args) {
        Observable<String> source =
                Observable.just("Alpha", "Beta", "Gamma", "Delta",
                        "Epsilon");
        source.flatMap(s -> Observable.fromArray(s.split("")))
                .subscribe(System.out::println);
    }
}

我們已經獲取了這五個字符串,從每一個映射了它們(通過flatMap())以。爲此,我們調用了每個字符串的split()方法,並向其傳遞了一個空的String參數“”,
每個字符都會分開。 這將返回一個包含所有字符的String []數組,其中
我們傳遞給Observable.fromArray()發出每個。 flatMap()希望每次發射都會產生一個Observable,
它將合併所有產生的Observable,並在單個流中發出它們的值。

這是另一個示例:讓我們採用一系列字符串值(每個字符串值串聯在一起)用“ /”分隔),在它們上使用flatMap(),並僅過濾數值,然後再將它們轉換爲
整數排放:

import io.reactivex.Observable;

public class Ch4_6 {
    public static void main(String[] args) {
        Observable<String> source =
                Observable.just("521934/2342/FOXTROT", "21962/12112/78886/TANGO", "283242/4542/WHISKEY/2348562");
        source.flatMap(s -> Observable.fromArray(s.split("/")))
                .filter(s -> s.matches("[0-9]+")) //use regex to filter integers
                .map(Integer::valueOf)
                .subscribe(System.out::println);
    }
}

我們用/字符將每個String分解,從而產生一個數組。 我們把它變成了一個Observable
在它上面使用了flatMap()來發出每個String。 我們僅使用常規過濾器過濾了數字字符串值
表達式[0-9] +(消除FOXTROT,TANGO和WHISKEY),然後將每個發射轉換爲Integer。

就像Observable.merge()一樣,您也可以將發射映射到無限的Observables並將它們合併。 例如,
我們可以從Observable 發出簡單的Integer值,但在它們上使用flatMap()來驅動
Observable.interval(),其中每個參數都用作句點參數。

在以下代碼段中,我們發出值2、3、10和7,它們將產生間隔可觀察到的東西,分別在2秒,3秒,10秒時發射
秒和7秒。 這四個Observable將合併爲一個流:

import io.reactivex.Observable;

import java.util.concurrent.TimeUnit;

public class Ch4_7 {
    public static void main(String[] args) {
        Observable<Integer> intervalArguments =
                Observable.just(2, 3, 10, 7);
        intervalArguments.flatMap(i ->
                Observable.interval(i, TimeUnit.SECONDS)
                        .map(i2 -> i + "s interval: " + ((i + 1) * i) + " seconds elapsed")
        ).subscribe(System.out::println);
        sleep(12000);
    }

    public static void sleep(long millis) {
        try {
            Thread.sleep(millis);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

輸出結果如下:

2s interval: 2 seconds elapsed

3s interval: 3 seconds elapsed

2s interval: 4 seconds elapsed

2s interval: 6 seconds elapsed

3s interval: 6 seconds elapsed

7s interval: 7 seconds elapsed

2s interval: 8 seconds elapsed

3s interval: 9 seconds elapsed

2s interval: 10 seconds elapsed

10s interval: 10 seconds elapsed

2s interval: 12 seconds elapsed

3s interval: 12 seconds elapsed

Observable.merge()運算符將接受固定數量的Observable源。 但是flatMap()將
動態地爲每個進入的排放不斷添加新的可觀測源。這意味着您可以不斷合併新傳入的Observable。

關於flatMap()的另一個快速說明是,它可以以許多巧妙的方式使用。 直到今天,我一直在尋找新的東西
使用方式。 但是,您可以發揮創意的另一種方法是評估flatMap()和弄清楚您要返回哪種Observable。 例如,如果我之前的示例發出了一個
向flatMap()發射0,這將破壞所得的Observable.interval()。 但是我可以使用if語句
檢查它是否爲0,然後返回Observable.empty(),如下面的代碼片段所示:

Observable<Integer> secondIntervals = Observable.just(2, 0, 3, 10, 7);
secondIntervals.flatMap(i -> {
   	 if (i == 0)
   		return Observable.empty();
   	 else
   		return Observable.interval(i, TimeUnit.SECONDS)
   	 .map(l -> i + "s interval: " + ((l + 1) * i) + " seconds elapsed");
}).subscribe(System.out::println);

當然,這可能太巧妙了,因爲您可以將filter()放在flatMap()之前,並過濾掉髮射
等於0。但是關鍵是您可以在flatMap()中評估發射並確定哪種類型要返回的可觀察值。

flatMap()也是獲取熱門Observable UI事件流的絕佳方法(例如 JavaFX或Android按鈕單擊)和flatMap()每個事件到整個過程
在flatMap()中。 故障和錯誤恢復可以完全在該範圍內處理flatMap(),因此流程的每個實例都不會中斷以後的按鈕單擊。

如果您不希望快速單擊按鈕以產生多個冗餘實例, 過程中,您可以使用doOnNext()禁用按鈕或利用switchMap()殺死上一個按鈕
流程,我們將在第7章“切換,限制,窗口化和緩衝。

請注意,flatMap()有很多風格和變體,我們將接受許多重載。
爲了簡潔起見,請不要深入瞭解。 我們可以傳遞第二個combiner參數,即
BiFunction <T,U,R> lambda,將原始發出的T值與每個平面映射的U值關聯,並
兩者都變成R值。 在前面的從每個字符串中發出字母的示例中,我們可以關聯
具有原始字符串發射的每個字母均從以下位置映射:

import io.reactivex.Observable;

public class Ch4_8 {
    public static void main(String[] args) {
        Observable<String> source =
                Observable.just("Alpha", "Beta", "Gamma", "Delta",
                        "Epsilon");
        source.flatMap(s -> Observable.fromArray(s.split("")),
                (s, r) ->
                        s + "-" + r)
                .subscribe(System.out::println);
    }
}

輸出結果如下:

Alpha-A

Alpha-l

Alpha-p

Alpha-h

Alpha-a

Beta-B

Beta-e

Beta-t

Beta-a

Gamma-G

我們還可以使用flatMapIterable()將每個T發射映射到Iterable 而不是Observable 。 它
然後將爲每個Iterable 發出所有R值,從而節省了將其轉換爲一個可觀察的。 也有可能映射到Singles(flatMapSingle()),maybes的flatMap()變體
(flatMapMaybe())和Completables(flatMapCompletable())。 其中許多重載也適用於concatMap(),接下來我們將介紹。

二、級聯

級聯與合併非常相似,但有一個重要的細微差別:它將激發以下元素:
每個都按指定的順序順序提供Observable。 它不會繼續前進到下一個Observable
直到當前的一個調用onComplete()。 這使得確保合併的Observable發射它們
排放有保證的順序。 但是,對於無限的Observables而言,這通常是一個糟糕的選擇,因爲
可觀察對象將無限期地阻塞隊列,並永遠使後續的可觀察對象等待。

我們將介紹用於級聯的工廠和運算符。 您會發現它們很像合併它們,除了它們具有順序行爲。

當您要確保Observable觸發其排放有序。 如果您不關心訂購,請選擇合併。

2.1、Observable.concat() 和 concatWith()

Observable.concat()工廠是等效於Observable.merge()的串聯。 它將結合
發射多個Observable,但會依次觸發每個Observable,僅在之後觸發onComplete()被調用。

在下面的代碼中,我們有兩個源Observables發出字符串。 我們可以使用Observable.concat()
排放第一個排放物,然後排放第二個排放物:

import io.reactivex.Observable;

public class Ch4_9 {
    public static void main(String[] args) {
        Observable<String> source1 =
                Observable.just("Alpha", "Beta", "Gamma", "Delta",
                        "Epsilon");
        Observable<String> source2 =
                Observable.just("Zeta", "Eta", "Theta");
        Observable.concat(source1, source2)
                .subscribe(i -> System.out.println("RECEIVED: " + i));
    }
}

輸出結果如下:

RECEIVED: Alpha

RECEIVED: Beta

RECEIVED: Gamma

RECEIVED: Delta

RECEIVED: Epsilon

RECEIVED: Zeta

RECEIVED: Eta

RECEIVED: Theta

這與前面的Observable.merge()示例的輸出相同。 但正如合併部分所述,我們應該使用Observable.concat()來保證排放排序,因爲合並不能保證。 您可以
還使用concatWith()運算符完成相同的操作,如以下代碼行所示:

source1.concatWith(source2)
	   .subscribe(i -> System.out.println("RECEIVED: " + i));

如果我們將Observable.concat()與無限的Observables一起使用,它將永遠從遇到的第一個對象中發出
並防止以下任何可觀察對象觸發。 如果我們想將無限的Observable放在任何地方在串聯操作中,可能最後指定它。 這樣可以確保它不會阻止任何
跟隨它的可觀察對象,因爲沒有。 我們還可以使用take()運算符來使無窮大觀測值有限。

在這裏,我們發射一個每秒發射一次的Observable,但僅從中發射兩次。 之後,它將調用onComplete()並將其處置。 然後,第二個Observable串聯後將永遠發出(或在此
(如果應用程序在五秒鐘後退出)。 由於第二個Observable是指定的最後一個在Observable.concat()中,它不會因爲無限而阻止任何後續的Observable:

import io.reactivex.Observable;

import java.util.concurrent.TimeUnit;

public class Ch4_10 {
    public static void main(String[] args) {
		//emit every second, but only take 2 emissions
        Observable<String> source1 =
                Observable.interval(1, TimeUnit.SECONDS)
                        .take(2)
                        .map(l -> l + 1) // emit elapsed seconds
                        .map(l -> "Source1: " + l + " seconds");
		//emit every 300 milliseconds
        Observable<String> source2 =
                Observable.interval(300, TimeUnit.MILLISECONDS)
                        .map(l -> (l + 1) * 300) // emit elapsed milliseconds
                        .map(l -> "Source2: " + l + " milliseconds");
        Observable.concat(source1, source2)
                .subscribe(i -> System.out.println("RECEIVED: " + i));
		//keep application alive for 5 seconds
        sleep(5000);
    }

    public static void sleep(long millis) {
        try {
            Thread.sleep(millis);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

輸出結果如下:

RECEIVED: Source1: 1 seconds

RECEIVED: Source1: 2 seconds

RECEIVED: Source2: 300 milliseconds

RECEIVED: Source2: 600 milliseconds

RECEIVED: Source2: 900 milliseconds

RECEIVED: Source2: 1200 milliseconds

RECEIVED: Source2: 1500 milliseconds

數組和Iterable <Observable >輸入也有串聯的對應項,就像有合併。 Observable.concatArray()工廠將在Observable []中依次觸發每個Observable。
數組。 Observable.concat()工廠還將接受Iterable <Observable >並觸發每個Observable 以相同的方式。

請注意,concatMap()有一些變體。 當您要將每個發射映射到時,請使用concatMapIterable()
Iterable 而不是Observable 。 它將爲每個Iterable 發出所有T值,從而節省了步驟和
把每個變成一個Observable 的開銷。 還有一個concatMapEager()運算符會急切地
訂閱接收到的所有可觀測源,並將緩存排放,直到輪到他們排放爲止。

2.2、concatMap()

就像有flatMap()可以動態合併每次發射產生的Observable一樣,這裏還有一個
並置副本,稱爲concatMap()。 如果您關心訂購和訂購,則應首選此操作員。
希望從每個發射映射的每個Observable在開始下一個發射之前完成。 進一步來說,
concatMap()將按順序合併每個映射的Observable並一次將其觸發。 它只會移動到
噹噹前一個調用onComplete()時,下一個可觀察到。 如果源排放更快地產生可觀測物
如果concatMap()不能從中發出,則這些Observable將排隊。

如果我們明確關心發射,我們前面的flatMap()示例將更適合concatMap()
訂購。 儘管此處的示例與flatMap()示例具有相同的輸出,但我們應使用concatMap()
當我們明確關心維護順序並想要處理每個映射的Observable時
依序:

import io.reactivex.Observable;

public class Ch4_11 {
    public static void main(String[] args) {
        Observable<String> source =
                Observable.just("Alpha", "Beta", "Gamma", "Delta",
                        "Epsilon");
        source.concatMap(s -> Observable.fromArray(s.split("")))
                .subscribe(System.out::println);
    }
}

輸出結果如下:

A

l

p

h

a

B

e

t

a

G

a

m

m

同樣,您不太可能希望使用concatMap()映射到無限的Observable。 盡你所能猜想,這將導致後續的Observable永遠不會觸發。 您可能會想使用flatMap()
相反,我們將在第6章併發和並行化中的併發示例中使用它。

三、雙關

討論合併和串聯之後,讓我們輕鬆進行合併操作。Observable.amb() 的工廠(amb表示模棱兩可)將接受Iterable <Observable >併發出
發射的第一個Observable的發射,其餘的則被處理。 第一個可觀察到的排放是其排放量通過的排放量。 當您有多個來源時,這將很有幫助
相同的數據或事件,而您想要最快的數據或事件。

在這裏,我們有兩個間隔源,並將它們與Observable.amb()工廠結合在一起。 如果一個發射
每秒,而其他每300毫秒,則後者將獲勝,因爲它將首先發出:

import io.reactivex.Observable;

import java.util.Arrays;
import java.util.concurrent.TimeUnit;

public class Ch4_12 {
    public static void main(String[] args) {
		//emit every second
        Observable<String> source1 =
                Observable.interval(1, TimeUnit.SECONDS)
                        .take(2)
                        .map(l -> l + 1) // emit elapsed seconds
                        .map(l -> "Source1: " + l + " seconds");
		//emit every 300 milliseconds
        Observable<String> source2 =
                Observable.interval(300, TimeUnit.MILLISECONDS)
                        .map(l -> (l + 1) * 300) // emit elapsed milliseconds
                        .map(l -> "Source2: " + l + " milliseconds");
		//emit Observable that emits first
        Observable.amb(Arrays.asList(source1, source2))
                .subscribe(i -> System.out.println("RECEIVED: " +
                        i));
		//keep application alive for 5 seconds
        sleep(5000);
    }

    public static void sleep(long millis) {
        try {
            Thread.sleep(millis);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

輸出結果如下:

RECEIVED: Source2: 300 milliseconds

RECEIVED: Source2: 600 milliseconds

RECEIVED: Source2: 900 milliseconds

RECEIVED: Source2: 1200 milliseconds

RECEIVED: Source2: 1500 milliseconds

RECEIVED: Source2: 1800 milliseconds

RECEIVED: Source2: 2100 milliseconds

您還可以使用ambWith()運算符,它將完成相同的結果:

//emit Observable that emits first
source1.ambWith(source2)
	   .subscribe(i -> System.out.println("RECEIVED: " + i));

您還可以使用Observable.ambArray()來指定varargs數組,而不是Iterable <Observable >。

四、壓縮

壓縮允許您從每個可觀察的源中獲取一個發射並將其合併爲一個發射。
每個Observable可以發出不同的類型,但是您可以將這些不同的發出的類型合併爲一個
發射。 這是一個示例,如果我們有一個Observable 和Observable ,我們可以分別壓縮
將String和Integer一對一地配對在一起,並將其與lambda連接:

import io.reactivex.Observable;

public class Ch4_13 {
    public static void main(String[] args) {
        Observable<String> source1 =
                Observable.just("Alpha", "Beta", "Gamma", "Delta",
                        "Epsilon");
        Observable<Integer> source2 = Observable.range(1, 6);
        Observable.zip(source1, source2, (s, i) -> s + "-" + i)
                .subscribe(System.out::println);
    }
}

輸出結果如下:

Alpha-1

Beta-2

Gamma-3

Delta-4

Epsilon-5

zip()函數同時接收Alpha和1,然後將它們配對成一個分隔的字符串一個破折號-並將其向前推。 然後,它接收到Beta和2,並將它們作爲
串聯等等。 來自一個Observable的發射必須等待與來自一個Observable的發射配對其他可觀察。 如果一個Observable調用onComplete(),而另一個Observable仍在等待發射配對,
這些排放物將減少,因爲它們無可比擬。 這發生在6發射因爲我們只有五絃發射。

您還可以使用zipWith()運算符完成此操作,如下所示:

source1.zipWith(source2, (s,i) -> s + "-" + i)

您最多可以將9個Observable實例傳遞給Observable.zip()工廠。 如果您還需要更多,可以傳遞Iterable <Observable >或使用zipArray()提供一個Observable []數組。 請注意,如果一個或多個
源產生的排放比另一個源快,zip()會將這些快速排放排隊,因爲它們等待較慢的排放源。 這可能會導致不良的性能問題,因爲每個
源隊列在內存中。 如果您只關心壓縮每個來源的最新發射,而不是壓縮趕上整個隊列,您將需要使用combineLatest(),我們將在本節後面介紹。

使用Observable.zipIterable()傳遞布爾值delayError參數以將錯誤延遲到所有源終止並且有一個int bufferSize來暗示每個元素的預期數量
隊列大小優化的來源。 您可以指定後者以提高性能某些情況下,通過在壓縮之前緩衝排放來實現。

使用Observable.interval()壓縮還有助於降低排放。 在這裏,我們壓縮每個字符串間隔1秒。 這會使每根琴絃的發射速度降低一秒鐘,但請記住這五根
串發射可能會排隊等待它們與間隔發射配對的時間:

import io.reactivex.Observable;

import java.time.LocalTime;
import java.util.concurrent.TimeUnit;

public class Ch4_14 {
    public static void main(String[] args) {
        Observable<String> strings =
                Observable.just("Alpha", "Beta", "Gamma", "Delta",
                        "Epsilon");
        Observable<Long> seconds =
                Observable.interval(1, TimeUnit.SECONDS);
        Observable.zip(strings, seconds, (s, l) -> s)
                .subscribe(s ->
                        System.out.println("Received " + s +
                                " at " + LocalTime.now())
                );
        sleep(6000);
    }

    public static void sleep(long millis) {
        try {
            Thread.sleep(millis);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

五、combineLatest()

Observable.combineLatest()工廠有點類似於zip(),但是對於從一個發射的每個發射在所有排放源中,它將立即與其他所有排放源耦合。 它不會
將每個排放源的未成對排放物排隊,而是對最新排放物進行緩存和配對。

在這裏,讓我們在兩個間隔Observable之間使用Observable.combineLatest(),第一個在300發射
毫秒,另一秒:

import io.reactivex.Observable;

import java.util.concurrent.TimeUnit;

public class Ch4_15 {
    public static void main(String[] args) {
        Observable<Long> source1 =
                Observable.interval(300, TimeUnit.MILLISECONDS);
        Observable<Long> source2 =
                Observable.interval(1, TimeUnit.SECONDS);
        Observable.combineLatest(source1, source2,
                (l1, l2) -> "SOURCE 1: " + l1 + " SOURCE 2: " + l2)
                .subscribe(System.out::println);
        sleep(3000);
    }

    public static void sleep(long millis) {
        try {
            Thread.sleep(millis);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

輸出結果如下:

SOURCE 1: 2 SOURCE 2: 0

SOURCE 1: 3 SOURCE 2: 0

SOURCE 1: 4 SOURCE 2: 0

SOURCE 1: 5 SOURCE 2: 0

SOURCE 1: 5 SOURCE 2: 1

SOURCE 1: 6 SOURCE 2: 1

SOURCE 1: 7 SOURCE 2: 1

SOURCE 1: 8 SOURCE 2: 1

SOURCE 1: 9 SOURCE 2: 1

SOURCE 1: 9 SOURCE 2: 2

這裏發生了很多事情,但是讓我們嘗試將其分解。 source1每300毫秒發射一次,但是前兩個發射尚無與source2配對的東西,後者每秒發射一次,並且
尚未發生排放。 最後,在一秒鐘後,source2將其第一個發射推爲0,並與來源1的最新排放2(第三排放)。 請注意,之前的兩個排放量0和1來自
由於第三個發射2現在是最新發射,因此完全忘記了source1。 然後source1以300毫秒的間隔先推3、4,然後推5,但0仍然是source2的最新發射,因此所有
三對。 然後,source2發出其第二個發射1,並與5配對,最後一個來自source2。

簡而言之,當一個源着火時,它與其他源的最新發射相結合。Observable.combineLatest()在組合UI輸入方面特別有用,因爲以前的用戶輸入經常
無關緊要的,只有最新的是值得關注的。

withLatestFrom()

與Observable.combineLatest()類似,但不完全相同的是withLatestfrom()運算符。 它將映射
將每個T發射與其他Observables的最新值合併起來,但是只需要一個每個其他可觀察物的發射:

import io.reactivex.Observable;

import java.util.concurrent.TimeUnit;

public class Ch4_16 {
    public static void main(String[] args) {
        Observable<Long> source1 =
                Observable.interval(300, TimeUnit.MILLISECONDS);
        Observable<Long> source2 =
                Observable.interval(1, TimeUnit.SECONDS);
        source2.withLatestFrom(source1,
                (l1, l2) -> "SOURCE 2: " + l1 + " SOURCE 1: " + l2
        ).subscribe(System.out::println);
        sleep(3000);
    }

    public static void sleep(long millis) {
        try {
            Thread.sleep(millis);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

輸出結果如下:

SOURCE 2: 0 SOURCE 1: 2

SOURCE 2: 1 SOURCE 1: 5

SOURCE 2: 2 SOURCE 1: 9

如您所見,source2每隔一秒發出一次,而source1每隔300毫秒發出一次。 當你
在source2上調用withLatestFrom()並將其傳遞給source1,它將與source1的最新發射合併,但是
不關心任何先前或之後的排放。

您最多可以將四個不同類型的Observable實例傳遞給withLatestFrom()。 如果您需要超過
您可以將其傳遞給Iterable <Observable >。

六、分組

使用RxJava可以實現的一項強大功能是按指定的密鑰將排放分組爲
單獨的Observables。 這可以通過調用groupBy()運算符來實現,該運算符接受lambda
將每個發射映射到一個鍵。 然後它將返回一個Observable <GroupedObservable <K,T >>,它發出一個
特殊的Observable類型稱爲GroupedObservable。 GroupedObservable <K,T>與其他任何Observable一樣,但是它
具有可作爲屬性訪問的鍵K值。 它將發射針對該給定映射的T排放
鍵。

例如,我們可以使用groupBy()運算符將Observable 的發射按每個String的分組
長度。 稍後我們將訂閱它,但這是我們聲明的方式:

import io.reactivex.Observable;
import io.reactivex.observables.GroupedObservable;

public class Ch4_17 {
    public static void main(String[] args) {
        Observable<String> source =
                Observable.just("Alpha", "Beta", "Gamma", "Delta", "Epsilon");
        Observable<GroupedObservable<Integer, String>> byLengths =
                source.groupBy(s -> s.length());
    }
}

我們可能需要在每個GroupedObservable上使用flatMap(),但是在該flatMap()操作中,我們可能
想要減少或收集那些公共密鑰排放量(由於這將返回Single,因此我們需要使用flatMapSingle())。 讓我們調用toList(),以便我們可以將發射作爲按其長度分組的列表進行發射:

import io.reactivex.Observable;
import io.reactivex.observables.GroupedObservable;

public class Ch4_18 {
    public static void main(String[] args) {
        Observable<String> source =
                Observable.just("Alpha", "Beta", "Gamma", "Delta",
                        "Epsilon");
        Observable<GroupedObservable<Integer, String>> byLengths =
                source.groupBy(s -> s.length());
        byLengths.flatMapSingle(grp -> grp.toList())
                .subscribe(System.out::println);
    }
}

輸出結果如下:

[Beta]

[Alpha, Gamma, Delta]

[Epsilon]

Beta是唯一具有長度4的發射,因此它是該長度鍵列表中的唯一元素。 Alpha,Beta,和Gamma的長度均爲5,因此它們是從相同的GroupedObservable發射項發射的,
長度爲5,並被收集到同一列表中。 Epsilon是唯一長度爲7的發射,因此列表中唯一的元素。

請記住,GroupedObservable也有一個getKey()方法,該方法返回用那GroupedObservable。 如果我們只是簡單地將每個GroupedObservable和
然後以它的形式連接長度鍵,我們可以這樣做:

import io.reactivex.Observable;
import io.reactivex.observables.GroupedObservable;

public class Ch4_19 {
    public static void main(String[] args) {
        Observable<String> source =
                Observable.just("Alpha", "Beta", "Gamma", "Delta",
                        "Epsilon");
        Observable<GroupedObservable<Integer, String>> byLengths =
                source.groupBy(s -> s.length());
        byLengths.flatMapSingle(grp ->
                grp.reduce("", (x, y) -> x.equals("") ? y : x + ", " + y)
                        .map(s -> grp.getKey() + ": " + s)
        ).subscribe(System.out::println);
    }
}

輸出結果如下:

4: Beta

5: Alpha, Gamma, Delta

7: Epsilon

密切注意,GroupedObservables是熱和冷Observable的怪異組合。 他們不冷
因爲它們不會將錯過的排放重播給第二個觀察者,但是會緩存排放並沖洗
將它們發送給第一位觀察員,確保不會丟失任何人。 如果您需要重播排放物,請將其收集到
列表,就像我們之前所做的一樣,然後針對該列表執行操作。 您還可以使用緩存運算符,
我們將在下一章中學習。

七、總結

在本章中,我們介紹了以各種有用方式組合可觀察對象的方法。合併有助於結合並同時發射多個可觀察物,並將其排放量合併爲一個
流。 flatMap()運算符特別重要,因爲動態合併派生的Observables從排放中獲得的收益在RxJava中打開了許多有用的功能。串聯類似於合併,但是它
按順序而不是一次觸發源Observable。與模棱兩可的結合使我們選擇第一個Observable發射併發射其發射。壓縮可讓您合併來自
多個可觀測值,而“最新合併”則每次都會合並每個來源的最新排放其中之一開火。最後,分組允許您將一個Observable分爲幾個GroupedObservable,每個
具有共同關鍵的排放。

花時間探索將可觀察對象結合起來並進行實驗以瞭解它們如何工作。它們對於解鎖RxJava中的功能並快速表示事件和數據轉換。我們來看一些
在第6章併發和並行化,我們還將介紹如何執行多任務和並行化。

發佈了32 篇原創文章 · 獲贊 10 · 訪問量 2萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章