28

is there a way to 'break' out of a groovy closure.

maybe something like this:

[1, 2, 3].each { 
  println(it)
  if (it == 2)
    break 
}

9 Answers9

23

I often forget that Groovy implements an "any" method.

[1, 2, 3].any
{   
   println it
   return (it == 2)
}​
GreenGiant
  • 4,930
  • 1
  • 46
  • 76
J. Skeen
  • 322
  • 1
  • 3
  • 5
17

12/05/2013 Heavily Edited.

Answering the question that was asked.

Is it possible to break out of a Closure?

You would "break" out of a closure by issuing the return keyword. However that isn't helpful in the example that is given. The reason for this is that the closure (think of it as a method) is called by the each method for every item in the collection.

If you run this example you will see it will print 1 then 3.

[1, 2, 3].each {
  if (it == 2) return
  println(it)
}

Why break in the context of each doesn't make sense.

To understand why you cannot break out of the each method like you could break out of a for loop you need to understand a bit of what is actually happening. Here is a gross simplification what the each method on a collection does.

myEach([0,1,3])

void myEach(List things) {
    for (i in things) {
        myEachMethod(i)
    }
}

void myEachMethod(Object it) { // this is your Closure
    if(it == 2) return
    println it
}

As you can see the closure is basically a method that can be passed around. Just as in java you cannot break from within method call or closure.

What to do instead of breaking from each.

In Groovy you are supposed to express your code using high level abstractions as such primitive looping is not idiomatic. For the example that you gave I would consider making use of findAll. For example:

[1,2,3].findAll { it < 2 }.each { println it }

I hope this helps you understand what is going on.

Answering the implied question.

Can you break out of the Collection.each iterations against your supplied closure?

You cannot break out of the each method without throwing and catching an exception as John Wagenleitner has said. Although I would argue that throwing and catching an exception in the name of flow control is a code smell and a fellow programmer might slap your hands.

Ben Doerr
  • 1,655
  • 1
  • 13
  • 23
  • Your code prints 1, 2, 3 - return will not return from a closure. – John Wagenleitner Aug 28 '09 at 11:19
  • 1
    Just wanted to clarify that return will not "break" out of the closure, it will just return the method executing the closure. In the above case, had there been a println below the return it only would have printed for 1 and 3, however 1, 2 and 3 still would have been printed by the first println. – John Wagenleitner Aug 28 '09 at 11:31
  • I think you are confused. "each" is a METHOD that takes a closure as an argument. Then iterates over the collection calling your closure and passing in the current object as an argument to the closure. I have updated my answer to make this more clear. – Ben Doerr Aug 28 '09 at 17:34
  • Yes, I was confused it was late :p. I think the OP wanted to return from the method that was executing the closure and was just trying to point out that "return" does not do that. By changing the code it prints the expected results, but I don't think it's what the OP had in mind. – John Wagenleitner Sep 01 '09 at 04:29
  • I believe it's exactly what he had in mind. `return` is used to "break" out of a closure, since as mentioned above, closures internally translate to a method invocation. – mxk Jan 31 '11 at 12:09
11

You can throw an exception:

try {
    [1, 2, 3].each { 
        println(it)
        if (it == 2)
            throw new Exception("return from closure") 
    }
} catch (Exception e) { }

Use could also use "findAll" or "grep" to filter out your list and then use "each".

[1, 2, 3].findAll{ it < 3 }.each{ println it }
John Wagenleitner
  • 10,967
  • 1
  • 40
  • 39
  • 13
    Wouldn't you agree that using Exceptions for control of flow operations is typically considered a poor choice or a code smell? – Ben Doerr Dec 05 '13 at 20:52
  • 1
    Yes. But it is a way to break out of closure. Of course, usually there are more appropriate methods such as find or findAll. – John Wagenleitner Dec 05 '13 at 22:11
9

Take a look at Best pattern for simulating continue in groovy closure for an extensive discussion.

Community
  • 1
  • 1
Robert Munteanu
  • 67,031
  • 36
  • 206
  • 278
7

Try to use any instead of each

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

The result : 1, 2, 3

Phat H. VU
  • 2,350
  • 1
  • 21
  • 30
3

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
}
sea-kg
  • 317
  • 2
  • 5
  • 1
    A great answer here. Most of the above focus on "you can't break out of each()" or other existing iterative methods that take a closure as an argument. This answer identifies that the problem is not breaking out of the closure, it's telling the iterative method to quit calling the closure. – MonetsChemist Nov 19 '20 at 15:54
3

There is an other solution. Although, that groovy stuff like each/find/any is quite cool: if it doesn't fit, don't use it. You can still use the plain old

for (def element : list)

Especially, if you want to leave the method, too. Now you are free to use continue/break/return as you like. The resulting code might not be cool, but it is easy and understandable.

Harry Developer
  • 260
  • 1
  • 3
  • 15
  • I'd go further: use the simplest solution possible unless you can see a reason why not. If you are super-fluent in Groovy you may find using `any` here with `return true` to be just as simple as this. But the point of closures is that they're designed to be passed around and manipulated. For a plain-old job, a plain-old tool will do just fine! – mike rodent Feb 12 '18 at 19:32
2

This is in support of John Wagenleiter's answer. Tigerizzy's answer is plain wrong. It can easily be disproved practically by executing his first code sample, or theoretically by reading Groovy documentation. A return returns a value (or null without an argument) from the current iteration, but does not stop the iteration. In a closure it behaves rather like continue.

You won't be able to use inject without understanding this.

There is no way to 'break the loop' except by throwing an exception. Using exceptions for this purpose is considered smelly. So, just as Wagenleiter suggests, the best practice is to filter out the elements you want to iterate over before launching each or one of its cousins.

Sodastream
  • 21
  • 1
0

With rx-java you can transform an iterable in to an observable.

Then you can replace continue with a filter and break with takeWhile

Here is an example:

import rx.Observable

Observable.from(1..100000000000000000)
          .filter { it % 2 != 1} 
          .takeWhile { it<10 } 
          .forEach {println it}
frhack
  • 4,862
  • 2
  • 28
  • 25