草庐IT

Java 8 Iterable.forEach() 与 foreach 循环

coder 2023-04-22 原文

以下哪个是 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 .除了违反保持简单,愚蠢的原则外,新奇的 forEach()至少有以下不足:

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

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

  • 无法处理已检查的异常 .实际上并没有禁止 Lambda 抛出已检查的异常,而是禁止诸如 Consumer 之类的常见功能接口(interface)。不要声明任何。因此,任何抛出受检异常的代码都必须将它们包装在 try-catch 中。或 Throwables.propagate() .但即使你这样做,也并不总是清楚抛出的异常会发生什么。它可能会在 forEach() 的某处被吞下。
  • 限流控制 .一个 return在 lambda 中等于 continue在 for-each 中,但没有等效于 break .执行返回值、短路或设置标志等操作也很困难(如果不违反无非最终变量规则,这会稍微缓解一些情况)。 "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."
  • 可能并行执行 ,这对于除了 0.1% 的代码需要优化之外的所有人来说都是一件可怕的事情。任何并行代码都必须经过深思熟虑(即使它不使用锁、 volatile 和传统多线程执行的其他特别讨厌的方面)。任何错误都很难找到。
  • 可能会影响性能 ,因为 JIT 无法将 forEach()+lambda 优化到与普通循环相同的程度,尤其是现在 lambdas 是新的。我所说的“优化”并不是指调用 lambdas 的开销(很小),而是指现代 JIT 编译器对运行代码执行的复杂分析和转换。
  • 如果您确实需要并行性,那么使用 ExecutorService 可能要快得多,而且难度也不大。 .流都是自动的(阅读:不太了解您的问题)并使用专门的(阅读:一般情况下效率低下)并行化策略( fork-join recursive decomposition )。
  • 使调试更加困惑 ,因为嵌套调用层次结构和,上帝保佑,并行执行。调试器可能会在显示来自周围代码的变量时出现问题,并且诸如单步执行之类的事情可能无法按预期工作。
  • 流通常更难编码、阅读和调试 .实际上,对于复杂的“fluent ” API 来说,这通常是正确的。复杂的单个语句、大量使用泛型和缺乏中间变量的组合会产生令人困惑的错误消息并阻碍调试。而不是“此方法没有类型 X 的重载”,您会收到一条错误消息,更接近于“您在某处弄乱了类型,但我们不知道在哪里或如何弄乱”。同样,您无法像将代码分解为多个语句并将中间值保存到变量中那样轻松地单步调试和检查调试器中的内容。最后,阅读代码并理解每个执行阶段的类型和行为可能并非易事。
  • 像拇指酸痛一样伸出 . Java 语言已经有了 for-each 语句。为什么用函数调用替换它?为什么鼓励在表达式中的某处隐藏副作用?为什么要鼓励笨拙的单线?将常规的 for-each 和新的 forEach 随意混合是不好的风格。代码应该使用成语(由于重复而很快被理解的模式),使用的成语越少,代码越清晰,决定使用哪个成语所花费的时间就越少(对于像我这样的完美主义者来说,这是一个很大的时间消耗! )。

  • 如您所见,我不是 forEach() 的忠实粉丝,除非它有意义。
    对我来说特别冒犯的是 Stream不实现 Iterable (尽管实际上有方法 iterator )并且不能在 for-each 中使用,只能与 forEach() 一起使用。我建议使用 (Iterable<T>)stream::iterator 将 Streams 转换为 Iterables .更好的选择是使用 StreamEx修复了许多 Stream API 问题,包括实现 Iterable .
    也就是说,forEach()对以下情况很有用:
  • 原子地迭代同步列表 .在此之前,用Collections.synchronizedList()生成的列表对于 get 或 set 之类的事情是原子的,但在迭代时不是线程安全的。
  • 并行执行(使用适当的并行流) .如果您的问题符合 Streams 和 Spliterators 中内置的性能假设,那么与使用 ExecutorService 相比,这可以为您节省几行代码。
  • 具体容器哪 ,就像同步列表一样,受益于控制迭代(尽管这主要是理论性的,除非人们能举出更多的例子)
  • 更干净地调用单个函数 通过使用 forEach()和方法引用参数(即 list.forEach (obj::someMethod) )。但是,请记住有关已检查异常、更困难的调试以及减少编写代码时使用的习惯用法数量的要点。

  • 我引用的文章:
  • Everything about Java 8
  • Iteration Inside and Out (正如另一位海报所指出的)

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

    关于Java 8 Iterable.forEach() 与 foreach 循环,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/16635398/

    有关Java 8 Iterable.forEach() 与 foreach 循环的更多相关文章

    1. ruby - 树顶语法无限循环 - 2

      我脑子里浮现出一些关于一种新编程语言的想法,所以我想我会尝试实现它。一位friend建议我尝试使用Treetop(Rubygem)来创建一个解析器。Treetop的文档很少,我以前从未做过这种事情。我的解析器表现得好像有一个无限循环,但没有堆栈跟踪;事实证明很难追踪到。有人可以指出入门级解析/AST指南的方向吗?我真的需要一些列出规则、常见用法等的东西来使用像Treetop这样的工具。我的语法分析器在GitHub上,以防有人希望帮助我改进它。class{initialize=lambda(name){receiver.name=name}greet=lambda{IO.puts("He

    2. ruby-on-rails - 在 Ruby 中循环遍历多个数组 - 2

      我有多个ActiveRecord子类Item的实例数组,我需要根据最早的事件循环打印。在这种情况下,我需要打印付款和维护日期,如下所示:ItemAmaintenancerequiredin5daysItemBpaymentrequiredin6daysItemApaymentrequiredin7daysItemBmaintenancerequiredin8days我目前有两个查询,用于查找maintenance和payment项目(非排他性查询),并输出如下内容:paymentrequiredin...maintenancerequiredin...有什么方法可以改善上述(丑陋的)代

    3. java - 等价于 Java 中的 Ruby Hash - 2

      我真的很习惯使用Ruby编写以下代码:my_hash={}my_hash['test']=1Java中对应的数据结构是什么? 最佳答案 HashMapmap=newHashMap();map.put("test",1);我假设? 关于java-等价于Java中的RubyHash,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.com/questions/22737685/

    4. ruby - RuntimeError(自动加载常量 Apps 多线程时检测到循环依赖 - 2

      我收到这个错误:RuntimeError(自动加载常量Apps时检测到循环依赖当我使用多线程时。下面是我的代码。为什么会这样?我尝试多线程的原因是因为我正在编写一个HTML抓取应用程序。对Nokogiri::HTML(open())的调用是一个同步阻塞调用,需要1秒才能返回,我有100,000多个页面要访问,所以我试图运行多个线程来解决这个问题。有更好的方法吗?classToolsController0)app.website=array.join(',')putsapp.websiteelseapp.website="NONE"endapp.saveapps=Apps.order("

    5. java - 从 JRuby 调用 Java 类的问题 - 2

      我正在尝试使用boilerpipe来自JRuby。我看过guide从JRuby调用Java,并成功地将它与另一个Java包一起使用,但无法弄清楚为什么同样的东西不能用于boilerpipe。我正在尝试基本上从JRuby中执行与此Java等效的操作:URLurl=newURL("http://www.example.com/some-location/index.html");Stringtext=ArticleExtractor.INSTANCE.getText(url);在JRuby中试过这个:require'java'url=java.net.URL.new("http://www

    6. java - 我的模型类或其他类中应该有逻辑吗 - 2

      我只想对我一直在思考的这个问题有其他意见,例如我有classuser_controller和classuserclassUserattr_accessor:name,:usernameendclassUserController//dosomethingaboutanythingaboutusersend问题是我的User类中是否应该有逻辑user=User.newuser.do_something(user1)oritshouldbeuser_controller=UserController.newuser_controller.do_something(user1,user2)我

    7. java - 什么相当于 ruby​​ 的 rack 或 python 的 Java wsgi? - 2

      什么是ruby​​的rack或python的Java的wsgi?还有一个路由库。 最佳答案 来自Python标准PEP333:Bycontrast,althoughJavahasjustasmanywebapplicationframeworksavailable,Java's"servlet"APImakesitpossibleforapplicationswrittenwithanyJavawebapplicationframeworktoruninanywebserverthatsupportstheservletAPI.ht

    8. Observability:从零开始创建 Java 微服务并监控它 (二) - 2

      这篇文章是继上一篇文章“Observability:从零开始创建Java微服务并监控它(一)”的续篇。在上一篇文章中,我们讲述了如何创建一个Javaweb应用,并使用Filebeat来收集应用所生成的日志。在今天的文章中,我来详述如何收集应用的指标,使用APM来监控应用并监督web服务的在线情况。源码可以在地址 https://github.com/liu-xiao-guo/java_observability 进行下载。摄入指标指标被视为可以随时更改的时间点值。当前请求的数量可以改变任何毫秒。你可能有1000个请求的峰值,然后一切都回到一个请求。这也意味着这些指标可能不准确,你还想提取最小/

    9. 【Java 面试合集】HashMap中为什么引入红黑树,而不是AVL树呢 - 2

      HashMap中为什么引入红黑树,而不是AVL树呢1.概述开始学习这个知识点之前我们需要知道,在JDK1.8以及之前,针对HashMap有什么不同。JDK1.7的时候,HashMap的底层实现是数组+链表JDK1.8的时候,HashMap的底层实现是数组+链表+红黑树我们要思考一个问题,为什么要从链表转为红黑树呢。首先先让我们了解下链表有什么不好???2.链表上述的截图其实就是链表的结构,我们来看下链表的增删改查的时间复杂度增:因为链表不是线性结构,所以每次添加的时候,只需要移动一个节点,所以可以理解为复杂度是N(1)删:算法时间复杂度跟增保持一致查:既然是非线性结构,所以查询某一个节点的时候

    10. 【Java入门】使用Java实现文件夹的遍历 - 2

      遍历文件夹我们通常是使用递归进行操作,这种方式比较简单,也比较容易理解。本文为大家介绍另一种不使用递归的方式,由于没有使用递归,只用到了循环和集合,所以效率更高一些!一、使用递归遍历文件夹整体思路1、使用File封装初始目录,2、打印这个目录3、获取这个目录下所有的子文件和子目录的数组。4、遍历这个数组,取出每个File对象4-1、如果File是否是一个文件,打印4-2、否则就是一个目录,递归调用代码实现publicclassSearchFile{publicstaticvoidmain(String[]args){//初始目录Filedir=newFile("d:/Dev");Datebeg

    随机推荐