RXJava2示例

重要要点

  • 响应式编程是用于处理异步数据流的规范
  • Reactive提供了用于转换和合并流以及管理流控制的工具
  • 大理石图提供了一个交互式画布,用于可视化React式结构
  • 类似于Java Streams API,但相似之处只是肤浅的
  • 附加到热流以衰减和处理异步数据馈送

在简化负载下的并发性的编程范例的不断发展中,我们已经看到采用java.util.concurrent,Akka流,CompletableFuture和诸如Netty之类的框架。 最近,由于React式编程的强大功能和强大的工具集,React式编程已Swift普及。

响应式编程是一种用于处理异步数据流,提供用于转换和合并流以及管理流控制的工具的规范,从而使您更容易推理整个程序设计。

但这并不容易,而且肯定存在学习曲线。 对于我们中间的数学家来说,这让人联想起从学习具有标量的标准代数到具有向量,矩阵和张量的线性代数的飞跃,本质上是将数据流视为一个单位。 与考虑对象的传统编程不同,React式推理的基本单元是事件流。 事件可以以对象,数据馈送,鼠标移动甚至异常的形式出现。 “例外”一词表达了特殊处理的传统概念,例如-这是应该发生的,这里是例外。 在被动React中,例外是头等公民,应有尽有。 由于流通常是异步的,因此抛出异常没有意义,因此,任何异常都作为事件在流中传递。

在本文中,我们将考虑React式编程的基础知识,并在教学上着眼于重要概念的内部化。

首先要牢记的是,在React式中,所有事物都是流。 可观察的是包装流的基本单位。 流可能包含零个或多个事件,并且可能会或可能不会完成,并且可能会或可能不会发出错误。 尽管有工具可以在发生异常时重试或替换不同的流,但是一旦流完成或发出错误,就可以完成。

在尝试我们的示例之前,请在代码库中包含RxJava依赖项。 您可以使用依赖项从Maven加载它:


    io.reactivex.rxjava2
    rxjava
    2.0.5

Observable类具有数十种静态工厂方法和操作符,每种方法和操作符具有多种风格, 用于生成新的Observable或将其附加到感兴趣的过程。 可观察变量是不可变的,因此运算符始终会产生一个新的可观察变量。 为了理解我们的代码示例,让我们回顾一下本文稍后在代码示例中将使用的基本Observable运算符。

Observable.just产生一个Observable,它发出一个通用实例,然后发出一个完整实例。 例如:

Observable.just("Howdy!")

创建一个新的Observable ,它在完成之前发出一个事件,即字符串“ Howdy!

您可以将该Observable分配给Observable变量

Observable hello = Observable.just("Howdy!");

但这本身不会使您走得太远,因为就像掉在森林里的那棵谚语树一样,如果没人在周围听到它,它也不会发出声音。 一个Observable必须有一个订阅者才能对其发出的事件进行任何处理。 值得庆幸的是,Java现在有了Lambdas,它使我们能够以简洁的声明式表达我们的可观察对象:

Observable howdy = Observable.just("Howdy!");
howdy.subscribe(System.out::println);

发出合群的“你好!”

像所有Observable方法一样, just关键字已重载,因此您也可以说

Observable.just("Hello", "World")
          .subscribe(System.out::println);

哪些输出

Hello
World

只是过载达10个输入参数。 注意,输出在两条单独的线上,指示两个单独的输出事件。

让我们尝试提供一个列表,看看会发生什么:

List words = Arrays.asList(
 "the",
 "quick",
 "brown",
 "fox",
 "jumped",
 "over",
 "the",
 "lazy",
 "dog"
);

Observable.just(words)
          .subscribe(System.out::println);

这会突然输出

[the, quick, brown, fox, jumped, over, the, lazy, dog]

我们原以为每个单词都是一个单独的发射,但是我们得到了一个由整个列表组成的发射。 为了解决这个问题,我们调用了更合适的fromIterable方法:

Observable.fromIterable(words)
          .subscribe(System.out::println);

它将数组或可迭代的事件转换为一系列事件,每个元素一个。

(请注意,在rxjava1中,有一个from方法的重载。它已被from fromIterable取代包括fromIterablefromArray 。)

执行该命令可提供更理想的多行输出:

the
quick
brown
fox
jumped
over
the
lazy
dog

对此进行编号会很高兴。 再次,为可观察者工作。

在编写代码之前,让我们研究一下两个运算符, 范围邮政编码。 range(i,n)创建一个以i开头的n个数字流。

Observable.range(1, 5).subscribe(System.out::println);

输出:

1
2
3
4
5

如果我们有办法将范围流与字流结合起来,那么我们解决加号问题就可以解决。

RX Marbles是一个平滑任何语言的React式学习曲线的好地方。 该网站具有许多React式操作的交互式JavaScript渲染。 每种方法都使用通用的“大理石”React性习语来描述一个或多个源流以及操作员产生的结果流。 时间从左到右流逝,事件以弹珠表示。 您可以单击并拖动源弹子,以查看它们如何影响结果。

快速阅读会发现 压缩 手术,正是医生命令的。 让我们看一下大理石图,以更好地理解它:

RXJava2示例_第1张图片

zip使用成对的“ zip”转换映射(可以以Lambda的形式提供)将源流的元素与提供的流的元素结合在一起。 这些流中的任何一个完成时,压缩流都将完成,因此来自另一个流的所有剩余事件都将丢失。 zip最多接受九个源流和zip操作。 有一个对应的zipWith运算符,用于将提供的流与现有流进行压缩

回到我们的例子。 我们可以使用range和zipWith来添加行号,并使用String.format作为我们的zip转换:

Observable.fromIterable(words)
 .zipWith(Observable.range(1, Integer.MAX_VALUE), 
     (string, count)->String.format("%2d. %s", count, string))
 .subscribe(System.out::println);

哪个输出:

1. the
 2. quick
 3. brown
 4. fox
 5. jumped
 6. over
 7. the
 8. lazy
 9. dog

看起来不错! 请注意,一旦任何流完成, zip和zipWith运算符就会停止从所有流中拉取。 这就是为什么我们不被Integer.MAX_VALUE上限吓到的原因

现在,我们要列出的不是单词,而是组成这些单词的字母。 这是flatMap的工作,它从Observable获取排放(对象,集合或数组),然后将这些元素映射到单个Observable,然后将所有排放量平化为单个Observable。

对于我们的示例,我们将使用split将每个单词转换成其组成字符的数组。 然后,我们将对它们进行平面映射,以创建一个由所有单词的所有字符组成的新Observable:

Observable.fromIterable(words)
 .flatMap(word -> Observable.fromArray(word.split("")))
 .zipWith(Observable.range(1, Integer.MAX_VALUE),
   (string, count) -> String.format("%2d. %s", count, string))
 .subscribe(System.out::println);

那输出

1. t
 2. h
 3. e
 4. q
 5. u
 6. i
 7. c
 8. k
 ...
30. l
31. a
32. z
33. y
34. d
35. o
36. g

所有的单词都存在并说明。 但是数据太多,我们只需要不同的字母:

Observable.fromIterable(words)
 .flatMap(word -> Observable.fromArray(word.split("")))
 .distinct()
 .zipWith(Observable.range(1, Integer.MAX_VALUE),
   (string, count) -> String.format("%2d. %s", count, string))
 .subscribe(System.out::println);

生产:

1. t
 2. h
 3. e
 4. q
 5. u
 6. i
 7. c
 8. k
 9. b
10. r
11. o
12. w
13. n
14. f
15. x
16. j
17. m
18. p
19. d
20. v
21. l
22. a
23. z
24. y
25. g

小时候,我被告知我们的“快速的棕色狐狸”短语包含英语字母中的每个字母,但是我们看到只有25个而不是26个。让我们对它们进行排序以帮助找到丢失的单词:

.flatMap(word -> Observable.fromIterable(word.split("")))
 .distinct()
 .sorted()
 .zipWith(Observable.range(1, Integer.MAX_VALUE),
   (string, count) -> String.format("%2d. %s", count, string))
 .subscribe(System.out::println);

产生:

1. a
 2. b
 3. c
 ...
17. q
18. r
19. t
20. u
21. v
22. w
23. x
24. y
25. z

似乎缺少字母19“ s”。 更正产生预期的输出

List words = Arrays.asList(
 "the",
 "quick",
 "brown",
 "fox",
 "jumped",
 "over",
 "the",
 "lazy",
 "dogs"
);

Observable.fromIterable(words)
 .flatMap(word -> Observable.fromArray(word.split("")))
 .distinct()
 .sorted()
 .zipWith(Observable.range(1, Integer.MAX_VALUE),
   (string, count) -> String.format("%2d. %s", count, string))
 .subscribe(System.out::println);

产生:

1. a
 2. b
 3. c
 4. d
 5. e
 6. f
 7. g
 8. h
 9. i
10. j
11. k
12. l
13. m
14. n
15. o
16. p
17. q
18. r
19. s
20. t
21. u
22. v
23. w
24. x
25. y
26. z

好多了!

到目前为止,所有这些看上去都与Java 8中引入的Java Streams API非常相似。但是相似之处完全是偶然的,因为React式添加了很多东西。

Java Streams和Lambda表达式是有价值的语言添加,但从本质上讲,它们毕竟不过是迭代集合并产生新集合的一种方式。 它们是有限的,静态的,并且不提供重用。 即使由Stream 并行运算符分叉,它们也会离开并执行自己的派生和联接,并且仅在完成后返回,从而使程序几乎不受控制。 相比之下,React式引入了定时,节流和流量控制的概念,它们可以附加到“无限”的过程中,这些过程可能永远不会结束。 输出不是集合,但是可以根据需要进行处理。

让我们看一下更多的大理石图以获得更好的图片。

合并运算符最多将九个源流合并到最终输出中,从而保持顺序。 无需担心竞争状况,所有事件都被“平化”到单个线程中,包括任何异常和完成事件。

防反跳运算符将指定时间延迟内的所有事件视为单个事件,仅发出每个此类序列中的最后一个:

RXJava2示例_第2张图片

您可以看到顶部“ 1”和底部“ 1”之间的时间差作为时间延迟。 在第2、3、4、5组中,每个元素的到达时间均小于前一个元素的时间延迟,因此将它们视为一个元素并消除了反弹。 如果我们将“ 5”移到延迟窗口的右边,它会启动一个新的去抖动窗口:

RXJava2示例_第3张图片

一个有趣的运算符是可疑的模糊运算符amb及其数组化身ambArray

amb是一个条件运算符,它从其所有输入流中选择发出并坚持该流的第一个流,而忽略所有其他流。 在下文中,第二个流是第一个泵送的,因此结果选择了该流并将其保留。

RXJava2示例_第4张图片

将第一流中的“ 20”滑到左侧,使顶部流成为第一生产者,从而产生更改后的输出:

RXJava2示例_第5张图片

例如,如果您有一个需要附加到提要上的过程(可能涉及多个消息主题,或者说彭博社和路透社),而您不在乎哪个,则只需要第一个并保持不变就可以使用。

ick虱

现在,我们有了工具来组合定时流,以产生有意义的混合信号。 在下一个示例中,我们考虑一个在一周中每秒泵送一次的供稿,但为了节省CPU,仅在周末中每三秒钟泵送一次。 我们可以使用该混合“节拍器”以所需的速度生成市场数据报价。

首先让我们创建一个布尔方法,该方法检查当前时间,并在周末返回true,在工作日返回false:

private static boolean isSlowTickTime() {
 return LocalDate.now().getDayOfWeek() == DayOfWeek.SATURDAY || 
        LocalDate.now().getDayOfWeek() == DayOfWeek.SUNDAY;
}

对于那些在IDE中关注的读者而言,他们可能不想等到下周末才能看到它的运行,可以替代以下实现,该实现快速运行15秒,然后缓慢运行15秒:

private static long start = System.currentTimeMillis();
public static Boolean isSlowTickTime() {
   return (System.currentTimeMillis() - start) % 30_000 >= 15_000;
}

让我们创建两个Observable, fastslow ,然后应用过滤来计划和合并它们。

我们将使用Observable.interval操作,该操作每指定数量的时间单位就会生成一个滴答(计算从0开始的连续Long )。

Observable fast = Observable.interval(1, TimeUnit.SECONDS);
Observable slow = Observable.interval(3, TimeUnit.SECONDS);

将每秒发出一个事件, 将每三秒钟发出一次。 (我们将忽略事件的Long值,我们只对时间感兴趣。)

现在,我们可以通过合并这两个可观察对象来生成同步时钟,对每个观察对象应用一个过滤器,以告诉快速流在工作日(或15秒)滴答,而慢流在周末(或15秒)滴答。 。

Observable clock = Observable.merge(
       slow.filter(tick-> isSlowTickTime()),
       fast.filter(tick-> !isSlowTickTime())
);

最后,让我们添加一个订阅以打印时间。 启动此程序将根据我们所需的时间表打印系统日期和时间。

clock.subscribe(tick-> System.out.println(new Date()));

您还需要保持活动状态以防止这种情况发生,因此请添加一个

Thread.sleep(60_000)

到方法的末尾(并处理InterruptedException)。

运行产生

Fri Sep 16 03:08:18 BST 2016
Fri Sep 16 03:08:19 BST 2016
Fri Sep 16 03:08:20 BST 2016
Fri Sep 16 03:08:21 BST 2016
Fri Sep 16 03:08:22 BST 2016
Fri Sep 16 03:08:23 BST 2016
Fri Sep 16 03:08:24 BST 2016
Fri Sep 16 03:08:25 BST 2016
Fri Sep 16 03:08:26 BST 2016
Fri Sep 16 03:08:27 BST 2016
Fri Sep 16 03:08:28 BST 2016
Fri Sep 16 03:08:29 BST 2016
Fri Sep 16 03:08:30 BST 2016
Fri Sep 16 03:08:31 BST 2016
Fri Sep 16 03:08:32 BST 2016
Fri Sep 16 03:08:35 BST 2016
Fri Sep 16 03:08:38 BST 2016
Fri Sep 16 03:08:41 BST 2016
Fri Sep 16 03:08:44 BST 2016
        . . .

您可以看到前15个滴答滴答间隔了一秒,随后是15秒的滴答滴答间隔了3秒,按要求交替显示。

附加到现有的提要

这对于从头开始创建Observable来抽取静态数据非常有用。 但是,如何将Observable附加到现有的提要,以便可以利用React流控制和流操纵策略?

RxJava2引入了一些新的类,我们在继续之前应该熟悉它们。

冷和热可观测物和可流动物

在以前的RxJava版本中,Observable配备了流控制方法,即使对于无关紧要的小流也是如此。 为了符合React式规范,RxJava2从Observable类中删除了流控制,并引入了Flowable类,该类实际上是一个提供流控制的Observable。

到目前为止,我们一直在讨论观测。 它们提供静态数据,尽管可能仍会调整时间。 冷观测值的区别在于,只有在有订户的情况下,它们才会抽水,并且所有订户都会收到完全相同的历史数据集,而不管他们何时订阅。 相比之下,热可观察对象将不考虑订户数量(如果有的话)而抽水,并且通常仅将最新数据泵送给所有订户(除非应用某些缓存策略。)可以通过执行以下两个步骤将冷可观察对象转换为热数据:

1.调用Observablepublish方法以产生一个新的ConnectableObservable
2.调用ConnectableObservableconnect方法以开始泵送。

这可行,但不支持任何流量控制。 通常,除了提供反压控件外,我们更喜欢使用Flowable并使用与Observables并行的语法连接到现有的长期运行的提要。

1a。 调用Flowablepublish方法以产生一个新的ConnectableFlowable
2a。 调用ConnectableFlowableconnect方法以开始泵送。

要附加到现有的提要,您可以(如果觉得很偏心)在提要中添加一个侦听器,以通过在每个报价上调用订户的onNext方法将报价传播到订户。 您的实现需要注意确保每个订阅者仍处于订阅状态,或者停止对其进行抽水,并且需要尊重背压语义。 幸运的是,所有这些工作都是由Flowablecreate 方法自动执行的。 对于我们的示例,假设我们有一个发布价格行情的SomeFeed市场数据服务,以及一个监听那些价格行情以及生命周期事件的SomeListener方法。 如果您想在家中尝试,可以在GitHub上实现这些实现 。

我们的Feed接受了一个监听器,该监听器支持以下API:

public void priceTick(PriceTick event);
public void error(Throwable throwable);

我们的PriceTick具有日期,工具和价格的访问器,以及一种用于通知最后一个报价的方法:

RXJava2示例_第6张图片

让我们看一个使用Flowable将Observable连接到实时Feed的示例:

1    SomeFeed feed = new SomeFeed<>(); 
2    Flowable flowable = Flowable.create(emitter -> { 
3        SomeListener listener = new SomeListener() { 
4            @Override 
5            public void priceTick(PriceTick event) { 
6                emitter.onNext(event); 
7                if (event.isLast()) { 
8                    emitter.onComplete(); 
9                } 
10           } 
11    
12           @Override 
13           public void error(Throwable e) { 
14               emitter.onError(e); 
15           } 
16       }; 
17       feed.register(listener); 
18   }, BackpressureStrategy.BUFFER); 
19   flowable.subscribe(System.out::println); 
20

这几乎是从Flowable Javadoc中逐字获取的; 它是这样工作的-Flowable包装了创建侦听器(第3行)和注册到服务(第17行)的步骤。 订户由Flowable自动附加。 服务生成的事件被委派给侦听器(第6行)。 第18行告诉观察者缓冲所有通知,直到订阅者使用它们为止。 其他背压选择包括:

BackpressureMode.MISSING不施加任何背压。 如果流无法跟上,则可能会引发MissingBackpressureException或IllegalStateException。

BackpressureStrategy.ERROR发出MissingBackpressureException如果下游无法跟上。

BackpressureStrategy .DROP丢弃传入onNext值,如果下游无法跟上。

BackpressureStrategy .LATEST保持最新onNext值和较新的,直到下游可以消耗它覆盖它。

所有这些都会产生冷的流动性。 就像任何冷观测一样,在第一个观察者订阅之前,不会出现滴答作响,并且所有订阅者都将收到相同的历史供稿集,这可能不是我们想要的。

要将其转换为可观察的热点,以便所有订阅者都能实时收到所有通知,我们必须调用发布并连接,如前所述:

21      ConnectableFlowable hotObservable = flowable.publish();
22      hotObservable.connect();

最后,我们可以订阅并显示价格变动:

23      hotObservable.subscribe((priceTick) -> 
24        System.out.printf("%s %4s %6.2f%n", priceTick.getDate(), 
25          priceTick.getInstrument(), priceTick.getPrice()));

翻译自: https://www.infoq.com/articles/rxjava2-by-example/?topicPageSponsorship=c1246725-b0a7-43a6-9ef9-68102c8d48e1

你可能感兴趣的:(RXJava2示例)