第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章并发和并行化,我们还将介绍如何执行多任务和并行化。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值