在 Java 中,流相对于循环的优势是啥? [关闭]

Posted

技术标签:

【中文标题】在 Java 中,流相对于循环的优势是啥? [关闭]【英文标题】:In Java, what are the advantages of streams over loops? [closed]在 Java 中,流相对于循环的优势是什么? [关闭] 【发布时间】:2017-10-26 01:55:23 【问题描述】:

我在一次采访中被问到这个问题,但我不相信我给出了最好的答案。我提到您可以进行并行搜索,并且 null 值是通过某种我不记得的方式处理的。现在我意识到我在考虑 Optionals。我在这里想念什么?他们声称这是更好或更简洁的代码,但我不确定我是否同意。


考虑到它的回答多么简洁,这似乎毕竟不是一个太宽泛的问题。


如果他们在面试时提出这个问题,而且很明显他们确实是,那么分解这个问题除了让找到答案变得更难之外还有什么目的?我的意思是,你在找什么?我可以分解问题并回答所有子问题,然后创建一个包含所有子问题链接的父问题......虽然看起来很愚蠢。在我们讨论的时候,请给我一个不太广泛的问题的例子。我知道没有办法只问这个问题的一部分,仍然得到一个有意义的答案。我可以用不同的方式问完全相同的问题。例如,我可以问“流服务的目的是什么?”或“我什么时候会使用流而不是 for 循环?”或“为什么要使用流而不是 for 循环?”这些都是完全相同的问题。

...或者它被认为过于宽泛,因为有人给出了一个非常长的多点答案?坦率地说,任何知情的人几乎可以对任何问题做到这一点。例如,如果您碰巧是 JVM 的作者之一,那么您可能整天都在谈论 for 循环,而我们大多数人都做不到。

“请编辑问题以将其限制为具有足够详细信息的特定问题,以确定适当的答案。避免一次提出多个不同的问题。请参阅如何提问页面以获得澄清此问题的帮助。” em>

如下所述,已经给出了一个充分的答案,证明存在一个并且很容易提供。

【问题讨论】:

这是基于恕我直言的意见。就个人而言,我更喜欢流,因为它使代码更具可读性。它允许写你想要的what,而不是how。此外,用单线做惊人的事情完全是坏事。 即使是30行,一个班轮?我不喜欢长链子。 此外,我在这里寻找的只是面试的适当回应。这是唯一重要的“意见”。 从教育的角度来说,这个问题也让我在以后的面试中也免了一些降级,@slim 确实做到了这一点,但从工业上讲,它也谈到了微软编程语言如何在抄袭 java 语言的基础上建立自己的职业生涯,最后java通过从对手那里扯掉Lambda表达式和流来报仇,让我们看看java将来会对结构和联合做什么:) 请注意,流仅利用函数式编程中的一小部分功能:-/ 【参考方案1】:

    您意识到错误:并行操作使用Streams,而不是Optionals。

    您可以定义使用流的方法:将它们作为参数,返回它们等。您不能定义将循环作为参数的方法。这允许一次复杂的流操作并多次使用它。请注意,Java 在这里有一个缺点:您的方法必须被称为 someMethod(stream) 而不是流自己的 stream.someMethod(),因此混合它们会使阅读变得复杂:尝试查看操作顺序

    myMethod2(myMethod(stream.transform(...)).filter(...))
    

    许多其他语言(C#、Kotlin、Scala 等)允许某种形式的“扩展方法”。

    即使您只需要顺序操作,并且不想重用它们,以便可以使用流或循环,对流的简单操作也可能对应于循环中相当复杂的变化。

【讨论】:

说明 1. Optional 接口不是链式处理空值的方式吗?关于 3,这是有道理的,因为使用短路的过滤器,该方法只会在指定的情况下被调用。高效的。我可以说使用它们可以减少编写需要测试的额外代码等的需要,这是有道理的。经过审查,我不确定您所说的 2 中的顺序案例是什么意思。 1. Optionalnull 的替代品,但它与并行操作没有任何关系。除非您的问题中的“现在我意识到我在考虑 Optionals”只是在谈论 null 处理? 我改变了 2 和 3 的顺序,并把它们都扩大了一点。【参考方案2】:

您循环遍历一个序列(数组、集合、输入...),因为您想对序列的元素应用一些函数。

流使您能够组合序列元素上的函数,并允许实现最常见的函数(例如映射、过滤、查找、排序、收集...... ) 独立于具体案例。

因此,在大多数情况下,给定一些循环任务,您可以使用 Streams 用更少的代码来表达它,即获得可读性

【讨论】:

嗯,这不仅仅是可读性。无需编写的代码就是无需测试的代码。 看来你的面试也有不错的答案【参考方案3】:

有趣的是,面试问题只问优点,不问缺点,因为两者都有。

流是一种更具声明性的样式。或者更表现力的风格。最好在代码中声明您的意图,而不是描述如何它是如何完成的:

 return people
     .filter( p -> p.age() < 19)
     .collect(toList());

... 非常清楚地表明您正在从列表中过滤匹配的元素,而:

 List<Person> filtered = new ArrayList<>();
 for(Person p : people) 
     if(p.age() < 19) 
         filtered.add(p);
     
 
 return filtered;

说“我正在循环”。循环的目的隐藏在逻辑中。

流通常更简洁。同一个例子说明了这一点。简洁并不总是更好,但如果你能同时简洁和富有表现力,那就更好了。

流与函数有很强的亲和力。 Java 8 引入了 lambda 和函数式接口,它打开了一个强大技术的完整玩具箱。流提供了将函数应用于对象序列的最方便、最自然的方式。

流鼓励减少可变性。这有点与函数式编程方面有关——您使用流编写的程序往往是您不修改对象的程序。

流鼓励更松散的耦合。您的流处理代码不需要知道流的来源或其最终终止方法。

流可以简洁地表达相当复杂的行为。例如:

 stream.filter(myfilter).findFirst();

乍一看好像过滤了整个流,然后返回第一个元素。但实际上findFirst() 驱动了整个操作,所以它在找到一项后有效地停止了。

流为未来的效率提升提供了空间。有些人进行了基准测试,发现来自内存中Lists 或数组的单线程流可能比等效循环慢。这是合理的,因为有更多的对象和开销在起作用。

但是流可以扩展。除了 Java 对并行流操作的内置支持外,还有一些使用 Streams 作为 API 的分布式 map-reduce 库,因为模型适合。

缺点?

性能:通过数组的for 循环在堆和 CPU 使用方面都非常轻量级。如果原始速度和内存节约是优先考虑的因素,那么使用流会更糟。

熟悉度。世界上到处都是经验丰富的程序程序员,他们来自多种语言背景,对他们来说循环很熟悉,流很新颖。在某些环境中,您希望编写这类人熟悉的代码。

认知开销。由于它的声明性质,以及对底层发生的事情的更多抽象,您可能需要建立一个新的代码与执行相关的心理模型。实际上,只有在出现问题,或者需要深入分析性能或细微错误时才需要这样做。当它“正常工作”时,它就正常工作。

调试器正在改进,但即使是现在,当您在调试器中单步执行流代码时,它可能比等效循环更难工作,因为简单循环非常接近变量和传统调试器使用的代码位置。

【讨论】:

我认为类似流的东西变得越来越普遍并且现在出现在许多不是特别面向 FP 的常用语言中是公平的。 考虑到这里列出的利弊,我认为除了非常简单的用途(if/then/else 逻辑很少,嵌套调用或 lambdas 等不多)之外,流不值得任何其他用途,在非-关键性能部件 @HenrikKjusAlstad 这绝对不是我打算交流的要点。流成熟、强大、富有表现力并且完全适合生产级代码。 哦,我不是说我不会在生产中使用它。但是,我会默认使用老式的循环/ifs 等,而不是流,特别是如果结果流看起来很复杂。我敢肯定,在某些用途中,流会击败循环并且 if 清晰,但通常情况下,我认为它是“绑定”的,有时甚至是相反的。因此,我会强调坚持旧方法的认知开销论点。 @lijepdam - 但是当“迭代列表”不是核心时,您仍然会有代码显示“我正在迭代此列表(请参阅循环内部以找出原因)”代码的意图。【参考方案4】:

抛开语法上的乐趣不谈,Streams 旨在处理可能无限大的数据集,而数组、集合以及几乎所有实现 Iterable 的 Java SE 类都完全在内存中。

Stream 的一个缺点是过滤器、映射等不能抛出已检查的异常。这使得 Stream 成为中间 I/O 操作的糟糕选择。

【讨论】:

当然,你也可以循环无限源。 但是如果要处理的元素保存在数据库中,你如何使用 Streams?初级开发人员可能会想在一个集合中阅读所有这些内容,只是为了使用 Streams。那将是一场灾难。 @LluisMartinez 一个好的 DB 客户端库将返回类似 Stream&lt;Row&gt; 的内容——或者可以编写自己的 Stream 实现包装 DB 结果游标操作。 @xxfelixxx 你可能想问一个关于它的新问题。仅这行代码看起来没有问题,因此请确保包含minimal reproducible example,以便我们自己查看您的问题。 @xxfelixxx 那行代码没有做任何事情。由于 this 从不调用该方法,因此该方法不会抛出异常。 Stream 不是空的,它只是未使用。就像将循环放入方法中,然后从不调用该方法。【参考方案5】:

我会说它的并行化非常好用。尝试使用 for 循环并行迭代数百万个条目。我们去很多cpu,不是更快;所以并行运行越容易越好,Streams 轻而易举。

我最喜欢的是他们提供的冗长。与如何他们如何做到这一点相比,了解他们实际所做的和生产的产品需要很少的时间。

【讨论】:

以上是关于在 Java 中,流相对于循环的优势是啥? [关闭]的主要内容,如果未能解决你的问题,请参考以下文章

T-trees 相对于 B+/-trees 的优势是啥?

Singleton类的优势是啥? [复制]

MySQLi相对于MySQL的优势[关闭]

Flink Kafka Stream 相对于 Spark Kafka Stream 的优势? Flink 上的 Kafka Stream 呢? [关闭]

Ngrx 相对于 Observable Data Services 架构的优势? [关闭]

Java具有闭包后的Scala优势[关闭]