我在一次采访中被问到这个问题,我不相信我给出了最好的答案。我提到您可以进行并行搜索,并且 null 值是通过某种我不记得的方式处理的。现在我意识到我在考虑 Optionals。我在这里想念什么?他们声称这是更好或更简洁的代码,但我不确定我是否同意。
考虑到它被回答得多么简洁,这似乎毕竟不是一个太宽泛的问题。
如果他们在面试时问这个问题,而且很明显他们是,那么分解它除了使找到答案变得更难之外,还有什么目的呢?我的意思是,你在找什么?我可以分解问题并回答所有子问题,然后创建一个包含所有子问题链接的父问题......虽然看起来很愚蠢。在我们讨论的时候,请给我一个不太广泛的问题的例子。我知道没有办法只问这个问题的一部分,仍然得到一个有意义的答案。我可以用不同的方式问完全相同的问题。例如,我可以问“流服务的目的是什么?” 或“我什么时候会使用流而不是 for 循环?” 或“为什么要使用流而不是 for 循环?” 这些都是完全相同的问题。
…还是因为有人给出了一个很长的多点答案而被认为过于宽泛?坦率地说,任何知情的人几乎可以对任何问题做到这一点。例如,如果您碰巧是 JVM 的作者之一,那么您可能整天都在谈论 for 循环,而我们大多数人都做不到。
“请编辑问题以将其限制为具有足够详细信息的特定问题,以确定适当的答案。避免一次提出多个不同的问题。请参阅如何提问页面以帮助澄清此问题。”
如下所述,已经给出了一个充分的答案,证明存在一个并且很容易提供。
有趣的是,面试问题只问优点,不问缺点,因为两者都有。
Streams 是一种更具声明性的风格 。或者更具 表现力 的风格。在代码中声明你的意图可能比描述它是 如何 完成的更好:
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()驱动整个操作,因此它在找到一项后有效地停止。
findFirst()
流为未来的效率提升提供了空间 。有些人进行了基准测试,发现来自内存或数组的单线程流List可能比等效循环慢。这是合理的,因为有更多的对象和开销在起作用。
List
但是流可以扩展。除了 Java 对并行流操作的内置支持外,还有一些使用 Streams 作为 API 的分布式 map-reduce 库,因为模型适合。
缺点?
性能 :for通过数组的循环在堆和 CPU 使用方面都非常轻量级。如果原始速度和内存节省是优先考虑的,那么使用流会更糟。
for
熟悉 度。世界上到处都是经验丰富的程序程序员,他们来自多种语言背景,对他们来说循环很熟悉,流很新颖。在某些环境中,您想编写这类人熟悉的代码。
认知开销 。由于它的声明性质,以及对底层发生的事情的更多抽象,您可能需要构建一个新的代码与执行相关的心理模型。实际上,只有在出现问题,或者需要深入分析性能或细微错误时才需要这样做。当它“正常工作”时,它就正常工作。
调试器 正在改进,但即使是现在,当您在调试器中单步执行流代码时,它可能比等效循环更难工作,因为简单循环非常接近传统调试器使用的变量和代码位置。