[groovy] Can you break from a Groovy "each" closure?

Is it possible to break from a Groovy .each{Closure}, or should I be using a classic loop instead?

This question is related to groovy

The answer is


You can't break from a Groovy each loop, but you can break from a java "enhanced" for loop.

def a = [1, 2, 3, 4, 5, 6, 7]

for (def i : a) {
    if (i < 2)
        continue
    if (i > 5)
        break
    println i
}

Output:

2
3
4
5

This might not fit for absolutely every situation but it's helped for me :)


Just using special Closure

// 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
}

Replace each loop with any closure.

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

    println element

    if (element == 3)
        return true // break
}

Output

1
3

No, you can't break from a closure in Groovy without throwing an exception. Also, you shouldn't use exceptions for control flow.

If you find yourself wanting to break out of a closure you should probably first think about why you want to do this and not how to do it. The first thing to consider could be the substitution of the closure in question with one of Groovy's (conceptual) higher order functions. The following example:

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

becomes

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

becomes

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

which also helps clarity. It states the intent of your code much better.

The potential drawback in the shown examples is that iteration only stops early in the first example. If you have performance considerations you might want to stop it right then and there.

However, for most use cases that involve iterations you can usually resort to one of Groovy's find, grep, collect, inject, etc. methods. They usually take some "configuration" and then "know" how to do the iteration for you, so that you can actually avoid imperative looping wherever possible.


I agree with other answers not to use an exception to break an each. I also do not prefer to create an extra closure eachWithBreak, instead of this I prefer a modern approach: let's use the each to iterate over the collection, as requested, but refine the collection to contain only those elements to be iterated, for example with findAll:

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

For example, if we what to break when the counter == 3 we can write this code (already suggested):

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

This will output

0
1
2

So far so good, but you will notice a small discrepancy though. Our end condition, negation of counter == 3 is not quite correct because !(counter==3) is not equivalent with it < 3. This is necessary to make the code work since findAll does not actually break the loop but continues until the end.

To emulate a real situation, let's say we have this code:

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

but we want to use each, so let's rewrite it using a function to simulate a break condition:

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

with the output:

0
1
2
4
5

now you see the problem with findAll. This does not stop, but ignores that element where the condition is not met.

To solve this issues, we need an extra variable to remember when the breaking condition become true. After this moment, findAll must ignore all remaining elements.

This is how it should look like:

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

with the output:

0
1
2

That's what we want!


(1..10).each{

if (it < 5)

println it

else

return false


You could break by RETURN. For example

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

It works for me!