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


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


If they are asking this question at interviews, and clearly they are, what purpose could breaking it down serve other than to make it harder to find an answer? I mean, what are you looking for? I could break down the question and have all the sub-questions answered but then create a parent question with links to all the subquestions... seems pretty silly though. While we are at it, please give me an example of a less broad question. I know of no way to ask only part of this question and still get a meaningful answer. I could ask exactly the same question in a different way. For example, I could ask "What purpose do streams serve?" or "When would I use a stream instead of a for loop?" or "Why bother with streams instead of for loops?" These are all exactly the same question though.

...还是因为有人给出了一个很长的多点答案而被认为太宽泛了?坦白说,任何知情的人都能回答任何问题。例如,如果你碰巧是JVM的作者之一,你可能会整天谈论for循环,而我们大多数人都不能。

“请编辑问题,将其限制在一个特定的问题上,并提供足够的细节以确定适当的答案。避免同时问多个不同的问题。请参见“如何询问”页面,以帮助澄清这个问题。”

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


当前回答

您循环遍历一个序列(数组、集合、输入……),因为您希望将某些函数应用于序列的元素。

流使您能够在序列元素上组合函数,并允许实现最常见的函数(例如映射,过滤,查找,排序,收集,…)而不依赖于具体情况。

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

其他回答

撇开语法的乐趣不谈,流被设计用来处理潜在的无限大的数据集,而数组、集合和几乎所有实现Iterable的Java SE类都完全在内存中。

Stream的一个缺点是过滤器、映射等不能抛出受控异常。这使得流对于中间I/O操作来说是一个糟糕的选择。

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

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

 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引入了lambdas和函数接口,这打开了一个充满强大技术的玩具箱。流提供了将函数应用于对象序列的最方便和自然的方法。

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

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

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

 stream.filter(myfilter).findFirst();

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

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

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

缺点呢?

性能:就堆和CPU使用而言,遍历数组的for循环是非常轻量级的。如果原始速度和内存节约是优先考虑的,那么使用流是更糟糕的。

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

认知的开销。由于它的声明性性质,以及从底层发生的事情中增加的抽象,您可能需要建立一个关于代码与执行之间关系的新思维模型。实际上,只有在出现问题时,或者需要深入分析性能或细微的bug时,才需要这样做。当它“只是工作”时,它就是工作。

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

我认为它的并行化很容易使用。尝试使用for循环并行迭代数百万个条目。我们有更多的cpu,而不是更快;所以越容易并行运行越好,与流这是一个微风。

我最喜欢的是它们提供的冗长。你只需要花很少的时间就能理解他们到底在做什么,生产什么,而不是他们是怎么做的。

您循环遍历一个序列(数组、集合、输入……),因为您希望将某些函数应用于序列的元素。

流使您能够在序列元素上组合函数,并允许实现最常见的函数(例如映射,过滤,查找,排序,收集,…)而不依赖于具体情况。

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

You realized incorrectly: parallel operations use Streams, not Optionals. You can define methods working with streams: taking them as parameters, returning them, etc. You can't define a method which takes a loop as a parameter. This allows a complicated stream operation once and using it many times. Note that Java has a drawback here: your methods have to be called as someMethod(stream) as opposed to stream's own stream.someMethod(), so mixing them complicates reading: try seeing the order of operations in myMethod2(myMethod(stream.transform(...)).filter(...)) Many other languages (C#, Kotlin, Scala, etc) allow some form of "extension methods". Even when you only need sequential operations, and don't want to reuse them, so that you could use either streams or loops, simple operations on streams may correspond to quite complex changes in the loops.