在Java中,流相对于循环的优势是什么?

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


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


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

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

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

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

143248 次浏览
  1. You realized incorrectly: parallel operations use Streams, not Optionals.

  2. 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".

  3. 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.

You loop over a sequence (array, collection, input, ...) because you want to apply some function to the elements of the sequence.

Streams give you the ability to compose functions on sequence elements and allow to implement most common functions (e.g. mapping, filtering, finding, sorting, collecting, ...) independent of a concrete case.

Therefore given some looping task in most cases you can express it with less code using Streams, i.e. you gain readability.

Interesting that the interview question asks about the advantages, without asking about disadvantages, for there are are both.

Streams are a more declarative style. Or a more expressive style. It may be considered better to declare your intent in code, than to describe how it's done:

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

... says quite clearly that you're filtering matching elements from a list, whereas:

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

Says "I'm doing a loop". The purpose of the loop is buried deeper in the logic.

Streams are often terser. The same example shows this. Terser isn't always better, but if you can be terse and expressive at the same time, so much the better.

Streams have a strong affinity with functions. Java 8 introduces lambdas and functional interfaces, which opens a whole toybox of powerful techniques. Streams provide the most convenient and natural way to apply functions to sequences of objects.

Streams encourage less mutability. This is sort of related to the functional programming aspect -- the kind of programs you write using streams tend to be the kind of programs where you don't modify objects.

Streams encourage looser coupling. Your stream-handling code doesn't need to know the source of the stream, or its eventual terminating method.

Streams can succinctly express quite sophisticated behaviour. For example:

 stream.filter(myfilter).findFirst();

Might look at first glance as if it filters the whole stream, then returns the first element. But in fact findFirst() drives the whole operation, so it efficiently stops after finding one item.

Streams provide scope for future efficiency gains. Some people have benchmarked and found that single-threaded streams from in-memory Lists or arrays can be slower than the equivalent loop. This is plausible because there are more objects and overheads in play.

But streams scale. As well as Java's built-in support for parallel stream operations, there are a few libraries for distributed map-reduce using Streams as the API, because the model fits.

Disadvantages?

Performance: A for loop through an array is extremely lightweight both in terms of heap and CPU usage. If raw speed and memory thriftiness is a priority, using a stream is worse.

Familiarity.The world is full of experienced procedural programmers, from many language backgrounds, for whom loops are familiar and streams are novel. In some environments, you want to write code that's familiar to that kind of person.

Cognitive overhead. Because of its declarative nature, and increased abstraction from what's happening underneath, you may need to build a new mental model of how code relates to execution. Actually you only need to do this when things go wrong, or if you need to deeply analyse performance or subtle bugs. When it "just works", it just works.

Debuggers are improving, but even now, when you're stepping through stream code in a debugger, it can be harder work than the equivalent loop, because a simple loop is very close to the variables and code locations that a traditional debugger works with.

Syntactic fun aside, Streams are designed to work with potentially infinitely large data sets, whereas arrays, Collections, and nearly every Java SE class which implements Iterable are entirely in memory.

A disadvantage of a Stream is that filters, mappings, etc., cannot throw checked exceptions. This makes a Stream a poor choice for, say, intermediate I/O operations.

I'd say its parallelization that is so easy to use. Try iterating over millions of entries in parallel with a for loop. We go to many cpus, not faster; so the easier it is to run in parallel the better, and with Streams this is a breeze.

What I like a lot is the verbosity they offer. It takes little time to understand what they actually do and produce as opposed of how they do it.