在Java8中,以下哪项是更好的实践?

Java 8:

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

Java 7:

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

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

EDIT

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

推荐答案

更好的做法是使用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-catchThrowables.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)解决了我提到的一些问题(当然,同时也增加了它们自己的复杂性).

Java相关问答推荐

RDX触发ChoiceBox转换器(并按字符串值排序)

使用ExecutorService时在ThreadFactory中触发自定义newThread函数

Java在模块化jar文件中找不到类,但是javap可以

Java 8中的多个字段和计数

使用动态ID从json获取详细信息的Jolt规范

使用意向过滤器从另一个应用程序启动服务

Java 21虚拟线程执行器的性能比池化操作系统线程的执行器差?

如何在Java中从XML中获取特定的 node ,然后将其删除?

GSON期间的Java类型擦除

为什么Spring Boot项目无法为基于MySQL的CRUD应用程序找到从JPARepository接口扩展的ProductRepository?

如何在不删除Java中已有内容的情况下覆盖文件内容?

在VS代码中,如何启用Java Main函数的&Q;Run|DEBUG&Q;代码?

根本不显示JavaFX阿拉伯字母

无法播放音频:从资源加载库GStreamer-Lite失败

错误:未找到扩展元素在JBossEAP 7.2中安装FUSE时出错

JavaFX标签中的奇怪字符

使IntelliJ在导入时优先 Select 一个类或将另一个标记为错误

Java中的一个错误';s stdlib SocksSocketImpl?

ResponseEntity.控制器截断响应的JSON部分

错误:JOIN/ON的参数必须是boolean类型,而不是bigint类型.Java Spring启动应用程序