36

I would like to return from a closure, like one would if using a break statement in a loop.

For example:

largeListOfElements.each{ element->
    if(element == specificElement){
        // do some work          
        return // but this will only leave this iteration and start the next 
    }
}

In the above if statement I would like to stop iterating through the list and leave the closure to avoid unnecessary iterations.

I've seen a solution where an exception is thrown within the closure and caught outside, but I'm not too fond of that solution.

Are there any solutions to this, other than changing the code to avoid this kind of algorithm?

John Topley
  • 107,187
  • 45
  • 188
  • 235
Craig
  • 739
  • 1
  • 9
  • 16

7 Answers7

27

I think you want to use find instead of each (at least for the specified example). Closures don't directly support break.

Under the covers, groovy doesn't actually use a closure either for find, it uses a for loop.

Alternatively, you could write your own enhanced version of find/each iterator that takes a conditional test closure, and another closure to call if a match is found, having it break if a match is met.

Here's an example:

Object.metaClass.eachBreak = { ifClosure, workClosure ->
    for (Iterator iter = delegate.iterator(); iter.hasNext();) {
        def value = iter.next()
        if (ifClosure.call(value)) {
            workClosure.call(value)
            break
        }        
    }
}

def a = ["foo", "bar", "baz", "qux"]

a.eachBreak( { it.startsWith("b") } ) {
    println "working on $it"
}

// prints "working on bar"
Ted Naleid
  • 25,313
  • 10
  • 68
  • 81
5

I think you're working on the wrong level of abstraction. The .each block does exactly what it says: it executes the closure once for each element. What you probably want instead is to use List.indexOf to find the right specificElement, and then do the work you need to do on it.

John Feminella
  • 281,997
  • 42
  • 326
  • 347
  • The OP is a bit ambiguous about what he wants: "In the above if statement I would like to stop iterating through the list and leave the closure to avoid unnecessary iterations." But I strongly suspect you're right! Not least, there is no "do some work" comment for the elements preceding the one of interest. – mike rodent Mar 15 '18 at 20:43
3

If you want to process all elements until a specific one was found you could also do something like this:

largeListOfElements.find { element ->
    // do some work
    element == specificElement
}

Although you can use this with any kind of "break condition". I just used this to process the first n elements of a collection by returning

counter++ >= n

at the end of the closure.

Machisuji
  • 670
  • 7
  • 16
1

After paulmurray's answer I wasn't sure myself what would happen with an Exception thrown from within a closure, so I whipped up a JUnit Test Case that is easy to think about:

class TestCaseForThrowingExceptionFromInsideClosure {

    @Test
    void testEearlyReturnViaException() {
        try {
            [ 'a', 'b', 'c', 'd' ].each {                 
                System.out.println(it)
                if (it == 'c') {
                    throw new Exception("Found c")
                } 
            }
        }
        catch (Exception exe) {
            System.out.println(exe.message)
        }
    }
}  

The output of the above is:

a
b
c
Found c

But remember that "one should NOT use Exceptions for flow control", see in particular this Stack Overflow question: Why not use exceptions as regular flow of control?

So the above solution is less than ideal in any case. Just use:

class TestCaseForThrowingExceptionFromInsideClosure {

    @Test
    void testEarlyReturnViaFind() {
        def curSolution
        [ 'a', 'b', 'c', 'd' ].find {                 
            System.out.println(it)
            curSolution = it
            return (it == 'c') // if true is returned, find() stops
        }
        System.out.println("Found ${curSolution}")
    }
}  

The output of the above is also:

a
b
c
Found c
Community
  • 1
  • 1
David Tonhofer
  • 12,954
  • 4
  • 44
  • 46
1

As I understand groovy, the way to shortcut these kinds of loops would be to throw a user-defined exception. I don't know what the syntax would be (not a grrovy programmer), but groovy runs on the JVM so it would be something something like:

class ThisOne extends Exception {Object foo; ThisOne(Object foo) {this.foo=foo;}}

try { x.each{ if(it.isOk()) throw new ThisOne(it); false} }
catch(ThisOne x) { print x.foo + " is ok"; }     
paulmurray
  • 3,227
  • 1
  • 20
  • 17
0

Today I faced a similar problem while working with each closure. I wanted to break the flow of execution based on my condition but couldn't do it.

The easiest way to do in groovy is to use any() on a list instead of each if you wish to return a boolean based on some condition.

Tarun Sapra
  • 1,691
  • 4
  • 24
  • 38
0

Good ole for loop still works in Groovy for your use case

for (element in largeListOfElements) {
    if(element == specificElement){
        // do some work          
        return
    }
}
Alexander Pogrebnyak
  • 42,921
  • 9
  • 97
  • 117