我在一次面试中被问到这个问题,我不相信我给出了最好的答案。我提到过你可以进行并行搜索,空值是通过某种我不记得的方式处理的。现在我意识到我想的是选修课。我错过了什么?他们声称这是更好或更简洁的代码,但我不确定我是否同意。
考虑到这个问题的回答是如此简洁,似乎这并不是一个太宽泛的问题。
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循环,而我们大多数人都不能。
“请编辑问题,将其限制在一个特定的问题上,并提供足够的细节以确定适当的答案。避免同时问多个不同的问题。请参见“如何询问”页面,以帮助澄清这个问题。”
如下所述,已经给出了一个充分的答案,证明存在一个答案,而且很容易提供。
有趣的是,面试问题只问了优点,而没有问缺点,因为两者都有。
流是一种更具声明性的风格。或者更有表现力的风格。在代码中声明你的意图可能比描述它是如何完成的更好:
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时,才需要这样做。当它“只是工作”时,它就是工作。
调试器正在改进,但即使是现在,当您在调试器中遍历流代码时,它也可能比等效的循环更加困难,因为简单的循环非常接近传统调试器使用的变量和代码位置。
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.