Java 8 Iterable.forEach() 与 foreach 循环 [英] Java 8 Iterable.forEach() vs foreach loop

查看:62
本文介绍了Java 8 Iterable.forEach() 与 foreach 循环的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

以下哪个是 Java 8 中更好的做法?

Java 8:

joins.forEach(join -> mIrc.join(mSession, join));

Java 7:

for (String join : joins) {mIrc.join(mSession, join);}

我有很多可以用 lambda 表达式简化"的 for 循环,但是使用它们真的有什么好处吗?它会提高它们的性能和可读性吗?

编辑

我还会将此问题扩展到更长的方法.我知道你不能从 lambda 返回或中断父函数,在比较它们时也应该考虑到这一点,但还有什么需要考虑的吗?

解决方案

更好的做法是使用 for-each.除了违反Keep It Simple, Stupid 原则,新奇的forEach() 至少还有以下不足:

  • 不能使用非最终变量.所以,像下面这样的代码不能变成 forEach lambda:
<块引用>

对象 prev = null;for(对象当前:列表){如果(上一个!= null )foo(prev, curr);上一个 = 当前;}

  • 无法处理检查异常.实际上并没有禁止 Lambda 抛出已检查的异常,但是像 Consumer 这样的常见功能接口并没有声明任何异常.因此,任何抛出已检查异常的代码都必须将它们包装在 try-catchThrowables.propagate() 中.但即使你这样做,也并不总是清楚抛出的异常会发生什么.它可能会在 forEach()

    的某个地方被吞下
  • 有限的流量控制.lambda 中的 return 等同于 for-each 中的 continue,但没有与 break 等价的东西.执行返回值、短路或设置标志(如果不违反无非最终变量 规则)."这不仅是一种优化,而且在您考虑时至关重要某些序列(例如读取文件中的行)可能会产生副作用,或者您可能拥有无限序列."

  • 可能会并行执行,这对除了 0.1% 的代码需要优化之外的所有人来说都是一件可怕的事情.任何并行代码都必须经过深思熟虑(即使它不使用锁、易失性和传统多线程执行的其他特别讨厌的方面).任何错误都很难找到.

  • 可能会影响性能,因为 JIT 无法将 forEach()+lambda 优化到与普通循环相同的程度,尤其是现在 lambda 是新的.通过优化"我指的不是调用 lambdas 的开销(很小),而是指现代 JIT 编译器对运行代码执行的复杂分析和转换.

  • 如果您确实需要并行性,那么使用 ExecutorService 可能会快得多,而且难度也不大.流都是自动的(阅读:不太了解您的问题)并且使用专门的(阅读:一般情况下效率低下)并行化策略(fork-join递归分解).

  • 使调试更加混乱,因为嵌套调用层次结构和并行执行.调试器可能会在显示周围代码中的变量时出现问题,并且单步执行等操作可能无法按预期工作.

  • 流通常更难编码、阅读和调试.实际上,对于复杂的fluent"也是如此.一般的 API.复杂的单个语句、大量使用泛型和缺乏中间变量的组合会产生令人困惑的错误消息并阻碍调试.而不是此方法没有类型 X 的重载";您会收到一条错误消息,类似于您在某处弄乱了类型,但我们不知道在哪里或如何弄乱".同样,您无法像将代码分解为多个语句并将中间值保存到变量中那样轻松地在调试器中逐步检查和检查事物.最后,阅读代码并了解每个执行阶段的类型和行为可能并非易事.

  • 像拇指酸痛一样突出.Java 语言已经有了 for-each 语句.为什么用函数调用替换它?为什么鼓励在表达式的某处隐藏副作用?为什么要鼓励笨重的单线?将常规的 for-each 和新的 forEach 随意混合是不好的风格.代码应该使用成语(由于重复而很快被理解的模式),使用的成语越少,代码越清晰,决定使用哪种成语所花费的时间就越少(对于像我这样的完美主义者来说,这是一个很大的时间消耗!).

如您所见,我不是 forEach() 的忠实粉丝,除非在有意义的情况下.

对我来说特别冒犯的是 Stream 没有实现 Iterable(尽管实际上有方法 iterator)并且不能在for-each,只有一个 forEach().我建议使用 (Iterable)stream::iterator 将 Streams 转换为 Iterables.更好的替代方法是使用 StreamEx,它修复了许多 Stream API 问题,包括实现 Iterable.

也就是说,forEach() 对以下情况很有用:

  • 以原子方式迭代同步列表.在此之前,使用 Collections.synchronizedList() 生成的列表在 get 或 set 等方面是原子的,但在迭代时不是线程安全的.

  • 并行执行(使用适当的并行流).如果您的问题符合 Streams 和 Spliterators 中内置的性能假设,那么与使用 ExecutorService 相比,这可以为您节省几行代码.

  • 特定容器,如同步列表,受益于迭代控制(尽管这在很大程度上是理论上的,除非人们可以提出更多示例)

  • 使用 forEach() 和方法引用参数(即 list.forEach (obj::someMethod))).但是,请记住有关已检查异常、更困难的调试以及减少编写代码时使用的习惯用法数量的要点.

我参考的文章:

看起来像 lambdas 的一些原始提案(例如 http://www.javac.info/closures-v06a.html Google Cache) 解决了我提到的一些问题(当然,同时增加了它们自己的复杂性).

Which of the following is better practice in Java 8?

Java 8:

joins.forEach(join -> mIrc.join(mSession, join));

Java 7:

for (String join : joins) {
    mIrc.join(mSession, join);
}

I have lots of for loops that could be "simplified" with lambdas, but is there really any advantage of using them? Would it improve their performance and readability?

EDIT

I'll also extend this question to longer methods. I know that you can't return or break the parent function from a lambda and this should also be taken into consideration when comparing them, but is there anything else to be considered?

解决方案

The better practice is to use for-each. Besides violating the Keep It Simple, Stupid principle, the new-fangled forEach() has at least the following deficiencies:

  • Can't use non-final variables. So, code like the following can't be turned into a forEach lambda:

Object prev = null;
for(Object curr : list)
{
    if( prev != null )
        foo(prev, curr);
    prev = curr;
}

  • Can't handle checked exceptions. Lambdas aren't actually forbidden from throwing checked exceptions, but common functional interfaces like Consumer don't declare any. Therefore, any code that throws checked exceptions must wrap them in try-catch or Throwables.propagate(). But even if you do that, it's not always clear what happens to the thrown exception. It could get swallowed somewhere in the guts of forEach()

  • Limited flow-control. A return in a lambda equals a continue in a for-each, but there is no equivalent to a break. It's also difficult to do things like return values, short circuit, or set flags (which would have alleviated things a bit, if it wasn't a violation of the no non-final variables rule). "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, which is a horrible, horrible thing for all but the 0.1% of your code that needs to be optimized. Any parallel code has to be thought through (even if it doesn't use locks, volatiles, and other particularly nasty aspects of traditional multi-threaded execution). Any bug will be tough to find.

  • Might hurt performance, because the JIT can't optimize forEach()+lambda to the same extent as plain loops, especially now that lambdas are new. By "optimization" I do not mean the overhead of calling lambdas (which is small), but to the sophisticated analysis and transformation that the modern JIT compiler performs on running code.

  • If you do need parallelism, it is probably much faster and not much more difficult to use an ExecutorService. Streams are both automagical (read: don't know much about your problem) and use a specialized (read: inefficient for the general case) parallelization strategy (fork-join recursive decomposition).

  • Makes debugging more confusing, because of the nested call hierarchy and, god forbid, parallel execution. The debugger may have issues displaying variables from the surrounding code, and things like step-through may not work as expected.

  • Streams in general are more difficult to code, read, and debug. Actually, this is true of complex "fluent" APIs in general. The combination of complex single statements, heavy use of generics, and lack of intermediate variables conspire to produce confusing error messages and frustrate debugging. Instead of "this method doesn't have an overload for type X" you get an error message closer to "somewhere you messed up the types, but we don't know where or how." Similarly, you can't step through and examine things in a debugger as easily as when the code is broken into multiple statements, and intermediate values are saved to variables. Finally, reading the code and understanding the types and behavior at each stage of execution may be non-trivial.

  • Sticks out like a sore thumb. The Java language already has the for-each statement. Why replace it with a function call? Why encourage hiding side-effects somewhere in expressions? Why encourage unwieldy one-liners? Mixing regular for-each and new forEach willy-nilly is bad style. Code should speak in idioms (patterns that are quick to comprehend due to their repetition), and the fewer idioms are used the clearer the code is and less time is spent deciding which idiom to use (a big time-drain for perfectionists like myself!).

As you can see, I'm not a big fan of the forEach() except in cases when it makes sense.

Particularly offensive to me is the fact that Stream does not implement Iterable (despite actually having method iterator) and cannot be used in a for-each, only with a forEach(). I recommend casting Streams into Iterables with (Iterable<T>)stream::iterator. A better alternative is to use StreamEx which fixes a number of Stream API problems, including implementing Iterable.

That said, forEach() is useful for the following:

  • Atomically iterating over a synchronized list. Prior to this, a list generated with Collections.synchronizedList() was atomic with respect to things like get or set, but was not thread-safe when iterating.

  • Parallel execution (using an appropriate parallel stream). This saves you a few lines of code vs using an ExecutorService, if your problem matches the performance assumptions built into Streams and Spliterators.

  • Specific containers which, like the synchronized list, benefit from being in control of iteration (although this is largely theoretical unless people can bring up more examples)

  • Calling a single function more cleanly by using forEach() and a method reference argument (ie, list.forEach (obj::someMethod)). However, keep in mind the points on checked exceptions, more difficult debugging, and reducing the number of idioms you use when writing code.

Articles I used for reference:

EDIT: Looks like some of the original proposals for lambdas (such as http://www.javac.info/closures-v06a.html Google Cache) solved some of the issues I mentioned (while adding their own complications, of course).

这篇关于Java 8 Iterable.forEach() 与 foreach 循环的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆