Java 8流和RxJava Observables之间的区别


144

Java 8流是否类似于RxJava可观察到的?

Java 8流定义:

java.util.stream包中的类提供了Stream API,以支持对元素流进行功能样式的操作。


8
仅供参考,有人建议在JDK 9中引入更多类似RxJava的类。jsr166
John Vint

@JohnVint此提案的状态如何。它真的会飞吗?
IgorGanapolsky '16

2
@IgorGanapolsky哦,是的,它看起来肯定会变成jdk9。cr.openjdk.java.net/~martin/webrevs/openjdk9/…。甚至还有一个供RxJava使用的端口,可用于github.com/akarnokd/RxJavaUtilConcurrentFlow
John Vint

我知道这是一个非常老的问题,但是我最近参加了Venkat Subramaniam的精彩演讲,该演讲对这一主题有深刻见解,并已更新为Java9:youtube.com/watch ? v=kfSSKM9y_0E 。对于研究RxJava的人来说可能很有趣。
Pedro '18

Answers:


152

TL; DR:所有序列/流处理库都为管道构建提供了非常相似的API。区别在于用于处理多线程和管道组成的API。

RxJava与Stream完全不同。在所有JDK中,最接近rx.Observable的可能是java.util.stream.Collector Stream + CompletableFuture组合(这需要处理额外的monad层,即必须处理Stream<CompletableFuture<T>>和之间的转换CompletableFuture<Stream<T>>)。

Observable和Stream之间存在显着差异:

  • 流是基于拉的,可观察对象是基于推的。这听起来可能太抽象了,但是会带来非常具体的重大后果。
  • 流只能使用一次,Observable可以多次订阅
  • Stream#parallel()将序列划分为多个分区,Observable#subscribeOn()Observable#observeOn()不是;Stream#parallel()用Observable 模拟行为是很棘手的,它曾经有.parallel()方法,但是这种方法引起了很大的混乱,以至于将.parallel()支持转移到github上的单独存储库RxJavaParallel上。更多细节在另一个答案中
  • Stream#parallel()不允许指定要使用的线程池,这与大多数接受可选Scheduler的RxJava方法不同。由于JVM中的所有流实例使用相同的fork-join池,因此添加.parallel()会意外地影响程序的另一个模块中的行为
  • 流缺乏像时间相关的操作Observable#interval()Observable#window()和其他许多人; 这主要是因为流是基于拉的,并且上游无法控制何时向下游发射下一个元素
  • 与RxJava相比,流提供的操作集受限制。例如流缺乏切断操作(takeWhile()takeUntil()); 解决方法的使用Stream#anyMatch()受到限制:这是终端操作,因此每个流最多只能使用一次
  • 从JDK 8开始,没有Stream#zip操作,有时这很有用
  • Streams很难自己构造,Observable可以通过多种方式构造。 编辑:如注释中所述,存在构造Stream的方法。但是,由于没有非终端短路,因此您不能例如轻松地在文件中生成行流(JDK提供了开箱即用的Files#lines和BufferedReader#lines,其他类似情况也可以通过构造Stream来管理来自Iterator)。
  • 可观察的报价资源管理工具(Observable#using());您可以使用它包装IO流或互斥量,并确保用户不会忘记释放资源-在订阅终止时会自动将其释放;Stream有onClose(Runnable)方法,但是您必须手动或通过try-with-resources调用它。例如 您必须记住,Files#lines()必须包含在try-with-resources块中。
  • 可观察对象始终同步(我实际上并未检查Streams是否同样如此)。这使您不必考虑基本操作是否是线程安全的(除非有错误,答案始终为“是”),但是与并发相关的开销仍然存在,无论您的代码是否需要它。

综述:RxJava与Streams明显不同。真正的RxJava替代品是ReactiveStreams的其他实现,例如Akka的相关部分。

更新。可以使用非默认的fork-join池Stream#parallel,请参见Java 8并行流中的自定义线程池

更新。以上所有内容均基于RxJava 1.x的经验。现在RxJava 2.x在这里,这个答案可能已经过时了。


2
为什么很难构建流?根据这篇文章,它似乎很容易:oracle.com/technetwork/articles/java/...
IgorGanapolsky

2
有很多类具有“流”方法:收集,输入流,目录文件等。但是,如果您想从自定义循环中创建流,例如遍历数据库游标,该怎么办?到目前为止,我发现的最好方法是创建一个Iterator,将其与Spliterator封装在一起,最后调用StreamSupport#fromSpliterator。简单案例恕我直言,胶水太多。也有Stream.iterate,但它产生无限的流。在这种情况下,切断串扰的唯一方法是Stream#anyMatch,但这是终端操作,因此您无法将流的生产者和消费者分开
Kirill Gamazkov

2
RxJava具有Observable.fromCallable,Observable.create等。或者,您可以安全地产生无限的Observable,然后说“ .takeWhile(condition)”,然后就可以将此序列发送给消费者了
Kirill Gamazkov

1
自己构建流并不难。您可以简单地调用Stream.generate()并传递自己的Supplier<U>实现,这只是一个简单的方法,您可以从中提供流中的下一项。还有许多其他方法。为了轻松构造Stream依赖于先前值的序列,您可以使用interate()方法,每个方法Collection都有一个stream()方法,并从varargs或数组Stream.of()构造a Stream。最后, StreamSupport它支持使用分隔符创建更高级的流或支持流原始类型。
jbx

“流缺少截止操作(takeWhile()takeUntil());” - JDK9有这些,我相信,在takeWhile()dropWhile()
阿卜杜勒·

50

Java 8 Stream和RxJava看起来非常相似。它们具有相似的运算符(过滤器,地图,flatMap ...),但并非为相同的用途而构建。

您可以使用RxJava执行asynchonus任务。

使用Java 8流,您将遍历集合中的项目。

您可以在RxJava(集合中的遍历项)中执行几乎相同的操作,但是,由于RxJava专注于并发任务,...它使用同步,闩锁,...因此,使用RxJava进行同一任务可能会比Java 8流。

可以将RxJava与进行比较CompletableFuture,但是它可以计算多个值。


12
值得注意的是,有关流遍历的声明仅适用于非并行流。 parallelStream支持简单遍历类似的同步/ MAPS /过滤等。
约翰Vint的

2
我不认为“因此,使用RxJava进行相同的任务可能比使用Java 8流慢。” 普遍适用,在很大程度上取决于手头的任务。
daschl '16

1
我很高兴您说使用RxJava进行相同的任务可能比使用Java 8流慢。这是许多RxJava用户不知道的非常重要的区别。
IgorGanapolsky '16

默认情况下,RxJava是同步的。您是否有任何基准可以支持您的说法可能会更慢?
MarcinKoziński'16

6
@marcin-koziński,您可以检查此基准:twitter.com/akarnokd/status/752465265091309568
dwursteisen 2016年

37

在技​​术和概念上存在一些差异,例如,Java 8流是一次性使用的,基于拉的同步值序列,而RxJava Observable是可重新观察的,基于自适应推挽的,潜在的异步值序列。RxJava面向Java 6+,也可在Android上运行。


4
涉及RxJava的典型代码大量使用了lambda,而lambda仅可从Java 8开始使用。因此,您可以在Java 6中使用Rx,但是代码会很吵
Kirill Gamazkov

1
类似的区别是Rx Observables可以无限期地存活直到被取消订阅。默认情况下,Java 8流以操作终止。
IgorGanapolsky '16

2
@KirillGamazkov可以使用retrolambda目标的Java 6的时候,使你的代码更漂亮
马辛科津斯基

Kotlin看起来比翻新更性感
Kirill Gamazkov

30

Java 8 Streams是基于pull的。您遍历消耗每个项目的Java 8流。这可能是无止境的。

RXJava Observable默认是基于推送的。您订阅了一个Observable,当下一项到达(onNext)或流完成时(onCompleted)或发生错误(onError)时,您将收到通知。因为随着Observable你收到onNextonCompletedonError事件,你可以做一些强大的功能,如不同的组合Observables到一个新的(zipmergeconcat)。您可以做的其他事情是缓存,限制……,并且它或多或少地使用了不同语言(RxJava,C#中的RX,RxJS等)的同一API。

默认情况下,RxJava是单线程的。除非您开始使用调度程序,否则所有事情都会在同一线程上发生。


在Stream中,您拥有forEach,与onNext几乎相同
paul

实际上,流通常是终端。“关闭流管道的操作称为终端操作。它们从诸如List,Integer甚至void(任何非Stream类型)之类的管道中产生结果。” 〜oracle.com/technetwork/articles/java/...
IgorGanapolsky

26

现有的答案是全面而正确的,但缺少针对初学者的明确示例。请允许我在“推拉式”和“可观察式”等术语后面加上一些具体含义。 注意:我讨厌这个词Observable(为天而生),因此仅指J8 vs RX流。

考虑一个整数列表,

digits = [1,2,3,4,5]

J8 Stream是用于修改集合的实用程序。例如,即使数字可以提取为

evens = digits.stream().filter(x -> x%2).collect(Collectors.toList())

这基本上是Python的map,filter,reduce,这是对Java的非常好(而且很早就应该过期)的补充。但是,如果没有提前收集到数字怎么办-如果数字在应用运行时流进来该怎么办-我们可以实时过滤偶数吗?

想象一个单独的线程进程在应用程序运行时随机输出整数(---表示时间)

digits = 12345---6------7--8--9-10--------11--12

在RX中,even可以每个新数字做出反应并实时应用过滤器

even = -2-4-----6---------8----10------------12

无需存储输入和输出列表。如果您需要输出列表,那么也没有问题。实际上,一切都是流。

evens_stored = even.collect()  

这就是为什么像“无状态”和“功能性”这样的术语更多地与RX相关联的原因


但是5甚至都不是……而且看起来J8 Stream是同步的,而Rx Stream是异步的?
富兰克林·于

1
@FranklinYu感谢我修复了5个错字。如果从同步与异步角度考虑较少,尽管可能是正确的,而从命令与功能角度考虑则更多。在J8中,您首先收集所有物品,然后再应用过滤器。在RX中,您可以定义独立于数据的过滤器功能,然后将其与偶数源(实时流或Java集合)相关联...这是一个完全不同的编程模型
Adam Hughes

我对此感到非常惊讶。我很确定Java流可以由数据流组成。是什么让您觉得相反呢?
维克·西维尤


3

Java 8 Streams可以利用多核体系结构有效地处理非常大的集合。相反,默认情况下,RxJava是单线程的(没有调度程序)。因此,除非您自己编写逻辑代码,否则RxJava将不会利用多核计算机。


4
默认情况下,Stream也是单线程的,除非您调用.parallel()。同样,Rx可以更好地控制并发性。
Kirill Gamazkov

@KirillGamazkov Kotlin协同流程(基于Java8流)现在支持结构化并发:kotlinlang.org/docs/reference/coroutines/flow.html#flows
IgorGanapolsky

没错,但是我对Flow和结构化并发一无所知。我的两点是:1)Stream和Rx都是单线程的,除非您明确地更改了它;2)Rx使您可以精确控制在哪个线程池上执行哪一步,而Streams只允许您说“使其以某种方式并行”
Kirill Gamazkov 19/12 / 15,21

我并没有真正理解“您需要什么线程池”这个问题。如您所说,“能够有效地处理非常大的馆藏”。或者,也许我希望任务的IO绑定部分可以在单独的线程池上运行。我认为我不了解您问题背后的意图。再试一次?
Kirill Gamazkov '19

1
Schedulers类中的静态方法允许获取预定义的线程池以及从Executor创建线程池。见reactivex.io/RxJava/2.x/javadoc/io/reactivex/schedulers/...
基里尔Gamazkov
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.