以下哪项是 Java 8 中更好的做法?
爪哇 8:
joins.forEach(join -> mIrc.join(mSession, join));
爪哇 7:
for (String join : joins) { mIrc.join(mSession, join); }
我有很多 for 循环可以用 lambdas “简化”,但是使用它们真的有什么好处吗?它会提高它们的性能和可读性吗?
编辑
我还将这个问题扩展到更长的方法。我知道你不能从 lambda 返回或破坏父函数,在比较它们时也应该考虑到这一点,但是还有什么需要考虑的吗?
更好的做法是使用for-each. 除了违反 Keep It Simple, Stupid 原则外,新奇forEach()还至少存在以下不足:
for-each
forEach()
Object prev = null; for(Object curr : list) { if( prev != null ) foo(prev, curr); prev = curr; }
无法处理检查异常 。Lambdas 实际上并没有被禁止抛出检查异常,但是常见的功能接口,比如Consumer不声明任何。因此,任何引发检查异常的代码都必须将它们包装在try-catchor中Throwables.propagate()。但即使你这样做了,也并不总是清楚抛出的异常会发生什么。它可能会被吞进肚子里的某个地方forEach()
Consumer
try-catch
Throwables.propagate()
有限的流量控制 。returnlambda 中的A等于continuefor-each 中的 a,但没有等价于 a break。做返回值、短路或 设置标志 之类的事情也很困难(如果不违反无非 最终变量 规则,这会稍微缓解一些事情)。“这不仅是一种优化,而且当您考虑到某些序列(例如读取文件中的行)可能有副作用,或者您可能有无限序列时,这一点至关重要。”
return
continue
break
可能会并行执行 ,这对于除了需要优化的 0.1% 的代码之外的所有人来说都是一件可怕的、可怕的事情。任何并行代码都必须经过深思熟虑(即使它不使用锁、易失性和传统多线程执行的其他特别讨厌的方面)。任何错误都很难找到。
可能会损害性能 ,因为 JIT 无法将 forEach()+lambda 优化到与普通循环相同的程度,尤其是现在 lambda 是新的。我所说的“优化”并不是指调用 lambdas 的开销(很小),而是指现代 JIT 编译器对运行代码执行的复杂分析和转换。
如果您确实需要并行性,那么使用 ExecutorService 可能会更快且难度不会太大 。流既是自动的(阅读:对您的问题了解不多) ,又 使用专门的(阅读:一般情况下效率低下)并行化策略(叉连接递归分解)。
*由于嵌套的调用层次结构和上帝禁止的并行执行, *使调试更加混乱。 调试器在显示周围代码中的变量时可能会出现问题,并且诸如单步执行之类的操作可能无法按预期工作。
通常,流更难编码、读取和调试 。实际上,一般来说,复杂的“流利”API 也是如此。复杂的单个语句、大量使用泛型以及缺少中间变量的组合共同产生了令人困惑的错误消息并阻碍了调试。而不是“此方法没有类型 X 的重载”,而是更接近于“您在某处弄乱了类型,但我们不知道在哪里或如何”的错误消息。同样,您不能像将代码分解为多个语句并将中间值保存到变量时那样在调试器中单步执行和检查事物。最后,阅读代码并了解每个执行阶段的类型和行为可能并非易事。
像拇指酸痛一样伸出来 。Java 语言已经有了 for-each 语句。为什么用函数调用代替它?为什么要鼓励在表达式的某处隐藏副作用?为什么要鼓励笨拙的单行字?将常规的 for-each 和新的 forEach 混用是不好的风格。代码应该用惯用语说话(由于重复而容易理解的模式),使用的惯用语越少,代码就越清晰,决定使用哪个惯用语的时间就越少(对于像我这样的完美主义者来说,这是一个很大的时间消耗! )。
如您所见,我不是 forEach() 的忠实拥护者,除非它有意义。
对我来说特别冒犯的是Stream它没有实现Iterable(尽管实际上有方法iterator)并且不能在 for-each 中使用,只能与 forEach() 一起使用。我建议使用(Iterable<T>)stream::iterator. 更好的选择是使用StreamEx,它修复了许多 Stream API 问题,包括实现Iterable.
Stream
Iterable
iterator
(Iterable<T>)stream::iterator
也就是说,forEach()对以下情况很有用:
以原子方式迭代同步列表 。在此之前,生成的列表Collections.synchronizedList()相对于 get 或 set 是原子的,但在迭代时不是线程安全的。
Collections.synchronizedList()
并行执行(使用适当的并行流) 。如果您的问题与 Streams 和 Spliterators 中内置的性能假设相匹配,那么与使用 ExecutorService 相比,这可以为您节省几行代码。
特定容器 ,如同步列表,受益于控制迭代(尽管这在很大程度上是理论上的,除非人们可以提出更多示例)
*通过使用forEach()和方法引用参数(即, ) *更干净地调用单个函数list.forEach (obj::someMethod)。但是,请记住检查异常、更难调试以及减少编写代码时使用的惯用语的数量。
list.forEach (obj::someMethod)
编辑: 看起来像 lambdas 的一些原始提案(例如http://www.javac.info/closures-v06a.html Google Cache)解决了我提到的一些问题(当然,同时增加了它们自己的复杂性)。