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

2022-08-31 06:31:32

我在一次面试中被问到这个问题,我不相信我给出了我能得到的最好的答案。我提到你可以做一个并行搜索,空值是通过一些我不记得的方式处理的。现在我意识到我正在考虑可选。我在这里错过了什么?他们声称这是更好或更简洁的代码,但我不确定我是否同意。


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


如果他们在面试中问这个问题,而且很明显他们是,那么除了让更难找到答案之外,分解它还有什么用呢?我的意思是,你在寻找什么?我可以分解问题并回答所有子问题,但随后创建一个父问题,其中包含指向所有子问题的链接...虽然看起来很傻。在我们讨论这个问题的时候,请给我举一个不那么宽泛的问题的例子。我知道没有办法只问这个问题的一部分,但仍然得到一个有意义的答案。我可以以不同的方式问完全相同的问题。例如,我可以问“流有什么用途?”或“我什么时候会使用流而不是for循环?”或者“为什么要打扰流而不是for循环?”不过,这些都是完全相同的问题。

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

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

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


答案 1

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

流是一种更具声明性的样式。或者更具表现力的风格。在代码中声明你的意图可能比描述它是如何完成的更好:

 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;

表示“我正在做一个循环”。循环的目的埋藏在逻辑的更深处。

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

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

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

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

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

 stream.filter(myfilter).findFirst();

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

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

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

弊?

性能:通过数组的循环在堆和 CPU 使用率方面都非常轻量级。如果原始速度和内存节俭是优先事项,则使用流会更糟。for

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

认知开销。由于其声明性,以及从下面发生的事情中增加的抽象,你可能需要建立一个新的心智模型,说明代码与执行的关系。实际上,您只需要在出现问题时执行此操作,或者需要深入分析性能或细微的错误。当它“只是工作”时,它就工作了。

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


答案 2

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

流的缺点是筛选器、映射等不能引发选中的异常。这使得 Stream 成为中间 I/O 操作的糟糕选择。


推荐