【问题标题】:Can you break from a Groovy "each" closure?你能摆脱 Groovy 的“每个”闭包吗?
【发布时间】:2011-03-04 05:58:57
【问题描述】:

是否可以从 Groovy 的 .each{Closure} break,或者我应该使用经典循环来代替?

【问题讨论】:

    标签: groovy


    【解决方案1】:

    不,您不能在不引发异常的情况下中止“每个”。如果您希望中断在特定条件下中止,您可能需要一个经典循环。

    或者,您可以使用“find”闭包而不是 each 并在您需要中断时返回 true。

    此示例将在处理整个列表之前中止:

    def a = [1, 2, 3, 4, 5, 6, 7]
    
    a.find { 
        if (it > 5) return true // break
        println it  // do the stuff that you wanted to before break
        return false // keep looping
    }
    

    打印

    1
    2
    3
    4
    5
    

    但不打印 6 或 7。

    使用接受闭包的自定义中断行为编写自己的迭代器方法也很容易:

    List.metaClass.eachUntilGreaterThanFive = { closure ->
        for ( value in delegate ) {
            if ( value  > 5 ) break
            closure(value)
        }
    }
    
    def a = [1, 2, 3, 4, 5, 6, 7]
    
    a.eachUntilGreaterThanFive {
        println it
    }
    

    也打印:

    1
    2
    3
    4
    5    
    

    【讨论】:

    • 我还向 groovy 提交了一个补丁,该补丁添加了一个 findResult 方法,该方法执行短路查找,从闭包返回第一个非空结果。这种方法可以用来覆盖几乎所有有人可能想早早突破的情况。检查补丁以查看它是否被接受并滚动到 groovy 中(我希望在 1.8 之前):jira.codehaus.org/browse/GROOVY-4253
    • 我试过这种情况:def a = [1, 2, 3, 4, 5, 6, 7, -1, -2] 它返回 1 2 3 4 5 -1 -2 .所以,“break”是行不通的。
    • 正确的发现是正确的选项,每一个都不会因返回而中断
    • findany 好 - 请参阅下面来自 @Michal 的另一个答案,一个对我有用
    • Ted Naleid 的 groovy 补丁非常有用。如果您确实需要查找操作的结果而不是元素本身,请改用 findResult。例如:​def test = [2] test.findResult{ it * 2 }​ 将返回 4 而不是 2
    【解决方案2】:

    any 闭包替换 each 循环。

    def list = [1, 2, 3, 4, 5]
    list.any { element ->
        if (element == 2)
            return // continue
    
        println element
    
        if (element == 3)
            return true // break
    }
    

    输出

    1
    3
    

    【讨论】:

    • 只是一个技巧。如果在“break”之后有一个语句会发生什么。这个语句在遇到“break”后仍然会执行。
    • @Phat H. VU 我添加了回报。 “break”后面的语句不会被执行
    • 感谢您的回复。你说得对。我也投票赞成你的答案。更多:any 方法在 DefaultGroovyMethods 中定义,它是一个谓词函数,如果集合中的元素满足提供的谓词闭包,则返回 true。
    • 以这种方式使用any() 有点误导,但它确实有效,并让您能够breakcontinue
    • 像@vegemite4me,我认为这是一个“技巧”,但您不太了解其中的含义。为了可维护性,您不应使用此解决方案。
    【解决方案3】:

    不,你不能在不抛出异常的情况下从 Groovy 中的闭包中中断。此外,您不应将异常用于控制流。

    如果您发现自己想要打破封闭,您可能应该首先考虑为什么要这样做,而不是如何去做。首先要考虑的可能是用 Groovy 的(概念上的)高阶函数之一来替换所讨论的闭包。下面的例子:

    for ( i in 1..10) { if (i < 5) println i; else return}
    

    变成

    (1..10).each{if (it < 5) println it}
    

    变成

    (1..10).findAll{it < 5}.each{println it} 
    

    这也有助于清晰。它更好地说明了代码的意图。

    所示示例中的潜在缺点是迭代仅在第一个示例中提前停止。如果您有性能方面的考虑,您可能希望立即停止它。

    但是,对于大多数涉及迭代的用例,您通常可以使用 Groovy 的 find、grep、collect、inject 等方法之一。他们通常会进行一些“配置”,然后“知道”如何为您进行迭代,以便您实际上可以尽可能避免命令式循环。

    【讨论】:

    【解决方案4】:

    只使用特殊的闭包

    // declare and implement:
    def eachWithBreak = { list, Closure c ->
      boolean bBreak = false
      list.each() { it ->
         if (bBreak) return
         bBreak = c(it)
      }
    }
    
    def list = [1,2,3,4,5,6]
    eachWithBreak list, { it ->
      if (it > 3) return true // break 'eachWithBreak'
      println it
      return false // next it
    }
    

    【讨论】:

    • 如果你有 10 亿行并且内部闭包在第一次调用时返回 true,那么你将迭代超过 10 亿减去一个值。 :(
    【解决方案5】:

    你不能从 Groovy 的每个循环中中断,但你可以从 java “增强”的 for 循环中中断。

    def a = [1, 2, 3, 4, 5, 6, 7]
    
    for (def i : a) {
        if (i < 2)
            continue
        if (i > 5)
            break
        println i
    }
    

    输出:

    2
    3
    4
    5
    

    这可能并不完全适合所有情况,但对我有帮助:)

    【讨论】:

      【解决方案6】:

      我同意其他答案,不要使用异常来破坏每个。我也不喜欢创建一个额外的闭包eachWithBreak,而不是这个我更喜欢一种现代的方法:让我们使用each 来迭代集合,根据要求,但优化集合以仅包含那些要迭代的元素,例如findAll:

      collection.findAll { !endCondition }.each { doSomething() }
      

      例如,如果我们要打破counter == 3时我们可以编写这段代码(已经建议):

      (0..5)
          .findAll { it < 3  }
          .each { println it }
      

      这将输出

      0
      1
      2
      

      到目前为止一切都很好,但是您会注意到一个小的差异。我们的结束条件,否定counter == 3 并不完全正确,因为!(counter==3) 不等于it &lt; 3。这是使代码正常工作所必需的,因为findAll 实际上并没有中断循环,而是一直持续到结束。

      为了模拟真实情况,假设我们有以下代码:

      for (n in 0..5) {
          if (n == 3)
              break
          println n
      }
      

      但是我们要使用each,所以让我们用一个函数来重写它来模拟中断条件:

      def breakWhen(nr) { nr == 3 }
      (0..5)
          .findAll { !breakWhen(it) }
          .each { println it }
      

      输出:

      0
      1
      2
      4
      5
      

      现在您看到了findAll 的问题。这不会停止,但会忽略不满足条件的元素。

      为了解决这个问题,我们需要一个额外的变量来记住中断条件何时变为真。此后,findAll 必须忽略所有剩余元素。

      它应该是这样的:

      def breakWhen(nr) { nr == 3 }
      def loop = true
      (0..5)
          .findAll {
              if (breakWhen(it))
                  loop = false
              !breakWhen(it) && loop
          } .each {
              println it
          }
      

      输出:

      0
      1
      2
      

      这就是我们想要的!

      【讨论】:

        【解决方案7】:

        (1..10).each{

        如果(它

        打印出来

        其他

        返回错误

        【讨论】:

        • 这不会脱离each,它根本不会打印大于 4 的值。else 是多余的,如果没有它,您的代码也会这样做。此外,如果您将println "not breaking" 放在else 之后和return false 之前,您可以证明each 不会与return false 中断。
        • 请尽量减少代码格式的可读性。回复时有一个视觉预览,并有很多说明如何操作
        【解决方案8】:

        您可以通过RETURN 休息。例如

          def a = [1, 2, 3, 4, 5, 6, 7]
          def ret = 0
          a.each {def n ->
            if (n > 5) {
              ret = n
              return ret
            }
          }
        

        它对我有用!

        【讨论】:

        • 这正是 OP 所要求的,尽管这显然不是他的意思。
        • 我能描述一下为什么会有反对票吗?在我看来,这与 +133 票的最佳答案所说的概念相同(解释较少)。
        • @Skepi 您不能“打破”关闭。您可以通过返回false 来破坏数组的any 方法。你不能以同样的方式破坏each 方法。
        猜你喜欢
        • 2022-06-11
        • 2021-11-12
        • 2011-08-07
        • 2014-10-27
        • 1970-01-01
        • 2015-03-09
        • 2012-04-25
        相关资源
        最近更新 更多