小编典典

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

all

我在一次采访中被问到这个问题,我不相信我给出了最好的答案。我提到您可以进行并行搜索,并且 null 值是通过某种我不记得的方式处理的。现在我意识到我在考虑
Optionals。我在这里想念什么?他们声称这是更好或更简洁的代码,但我不确定我是否同意。


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


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

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

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

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


阅读 62

收藏
2022-07-29

共1个答案

小编典典

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

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()驱动整个操作,因此它在找到一项后有效地停止。

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

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

缺点?

性能for通过数组的循环在堆和 CPU 使用方面都非常轻量级。如果原始速度和内存节省是优先考虑的,那么使用流会更糟。

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

认知开销
。由于它的声明性质,以及对底层发生的事情的更多抽象,您可能需要构建一个新的代码与执行相关的心理模型。实际上,只有在出现问题,或者需要深入分析性能或细微错误时才需要这样做。当它“正常工作”时,它就正常工作。

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

2022-07-29