更好的做法是使用for-each
.除了违反Keep It Simple, Stupid原则外,新款forEach()
还至少存在以下缺陷:
- Can't use non-final variables美元.因此,不能将如下代码转换为forEach lambda:
Object prev = null;
for(Object curr : list)
{
if( prev != null )
foo(prev, curr);
prev = curr;
}
Can't handle checked exceptions.实际上并不禁止lambda抛出判断过的异常,但是像Consumer
这样的常见函数接口没有声明任何异常.因此,任何引发判断异常的代码都必须将它们包装在try-catch
或Throwables.propagate()
中.但是,即使您这样做了,抛出的异常会发生什么情况也不总是很清楚.它可能会被吞噬在forEach()
年的毅力里的某个地方
Limited flow-control.λ中的return
等于A中的continue
,但没有与break
相等的值.还很难做到返回值、短路或set flags(如果不是违反no non-final variables规则的话,这会稍微缓解一些问题)."This is not just an optimization, but critical when you consider that some sequences (like reading the lines in a file) may have side-effects, or you may have an infinite sequence."
Might execute in parallel,这对于除了需要优化的0.1%的代码之外的所有人来说都是非常非常可怕的事情.任何并行代码都必须经过深思熟虑(即使它不使用锁、挥发性和传统多线程执行的其他特别令人讨厌的方面).任何错误都很难找到.
Might hurt performance,因为JIT不能像普通循环那样优化for Each()+lambda,特别是现在lambdas是新的.我所说的"优化"并不是指调用lambdas的开销(很小),而是指现代JIT编译器在运行代码时执行的复杂分析和转换.
If you do need parallelism, it is probably much faster and not much more difficult to use an ExecutorService.流都是自动的(读:不太了解你的问题)and使用专门的(读:一般情况下效率低下)并行化策略(fork-join recursive decomposition).
Makes debugging more confusing,因为嵌套的调用层次 struct 和并行执行.调试器可能会在显示周围代码中的变量时出现问题,而step-through等操作可能无法按预期工作.
Streams in general are more difficult to code, read, and debug.事实上,一般来说,复杂的"fluent"API也是如此.复杂的单语句、大量使用泛型以及缺少中间变量的组合,共同产生了令人困惑的错误消息,并阻碍了调试.而不是"这个方法没有类型X的重载",你会得到一条错误消息,更接近"你把类型搞砸了,但我们不知道在哪里或如何."类似地,在调试器中,你不能像代码被分解成多个语句、中间值被保存到变量中那样容易地单步执行和判断事情.最后,阅读代码并理解执行的每个阶段的类型和行为可能非常重要.
Sticks out like a sore thumb.Java语言已经有了for-each语句.为什么要用函数调用替换它?为什么鼓励在表达中隐藏副作用?为什么要鼓励笨拙的一行呢?随意地把每个人都有规律地和每个人都有规律地混在一起是不好的风格.代码应该使用惯用语(由于重复而易于理解的模式),使用的惯用语越少,代码就越清晰,决定使用哪种惯用语的时间也就越少(对于像我这样的完美主义者来说,这是一个巨大的时间消耗!).
如您所见,我不是forEach()的忠实粉丝,除非它有意义.
特别令我反感的是,Stream
没有实现Iterable
(尽管实际上有方法iterator
),并且不能在for-each中使用,只能与forEach()一起使用.我建议使用(Iterable<T>)stream::iterator
将流投射到迭代中.更好的替代方案是使用StreamEx,它可以修复许多Stream API问题,包括实现Iterable
.
也就是说,forEach()
对以下方面很有用:
Atomically iterating over a synchronized list.在此之前,使用Collections.synchronizedList()
生成的列表对于get或set之类的内容是原子的,但在迭代时不是线程安全的.
Parallel execution (using an appropriate parallel stream)美元.如果您的问题符合流和拆分器中内置的性能假设,那么与使用ExecutorService相比,这可以为您节省几行代码.
与同步列表一样,Specific containers which也受益于控制迭代(尽管这在很大程度上是理论上的,除非人们能提出更多示例)
Calling a single function more cleanly使用forEach()
和方法引用参数(即list.forEach (obj::someMethod)
).但是,请记住以下几点:判断异常、更困难的调试,以及在编写代码时减少使用习惯用法的数量.
我参考的文章有:
EDIT:看起来像是针对lambdas的一些原始建议(比如http://www.javac.info/closures-v06a.htmlGoogle Cache)解决了我提到的一些问题(当然,同时也增加了它们自己的复杂性).