Как вернуться из заводного замыкания и остановить его выполнение? - PullRequest
32 голосов
/ 19 апреля 2009

Я хотел бы вернуться из замыкания, как если бы я использовал оператор break в цикле.

Например:

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

В приведенном выше операторе if я хотел бы прекратить перебирать список и оставить закрытие, чтобы избежать ненужных итераций.

Я видел решение, в котором исключение было выброшено внутри замыкания и поймано снаружи, но я не слишком люблю это решение.

Есть ли какие-либо решения для этого, кроме изменения кода, чтобы избежать такого рода алгоритма?

Ответы [ 6 ]

27 голосов
/ 20 апреля 2009

Я думаю, что вы хотите использовать find вместо каждого (по крайней мере, для указанного примера). Закрытия не поддерживают разрыв напрямую.

Под крышками groovy на самом деле не использует замыкание для поиска, он использует цикл for.

В качестве альтернативы, вы можете написать свою собственную улучшенную версию find / каждого итератора, которая принимает условное замыкание теста и другое закрытие для вызова, если совпадение найдено, с прерыванием, если совпадение найдено.

Вот пример:

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"
5 голосов
/ 19 апреля 2009

Я думаю, что вы работаете на неправильном уровне абстракции. Блок .each делает именно то, что говорит: он выполняет замыкание один раз для каждого элемента. Вместо этого вы, вероятно, захотите использовать List.indexOf, чтобы найти правильный specificElement, а затем выполнить ту работу, которую вам нужно сделать для этого.

4 голосов
/ 13 декабря 2009

Если вы хотите обработать все элементы до тех пор, пока не будет найден определенный элемент, вы также можете сделать что-то вроде этого:

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

Хотя вы можете использовать это с любым видом "условия разрыва". Я просто использовал это для обработки первых n элементов коллекции, возвращая

counter++ >= n

в конце закрытия.

1 голос
/ 16 февраля 2013

После ответа Полмуррея я сам не был уверен, что произойдет с Исключением, выданным из замыкания, поэтому я создал тестовый пример JUnit, о котором легко подумать:

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

Вывод выше:

a
b
c
Found c

Но помните, что "нельзя использовать исключения для управления потоком" , см., В частности, вопрос переполнения стека: Почему бы не использовать исключения в качестве регулярного потока управления?

Так что вышеприведенное решение в любом случае не идеально. Просто используйте:

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}")
    }
}  

Вывод вышеупомянутого также:

a
b
c
Found c
1 голос
/ 13 августа 2010

Как я понимаю, Groovy способ сократить цикл такого рода - это создать пользовательское исключение. Я не знаю, какой будет синтаксис (не grrovy программист), но Groovy работает на JVM, так что это будет что-то вроде:

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"; }     
0 голосов
/ 29 марта 2019

Сегодня я сталкивался с подобной проблемой при работе с каждым замыканием. Я хотел прервать процесс выполнения в зависимости от моего состояния, но не смог этого сделать.

Самый простой способ сделать это в groovy - это использовать any () в списке вместо каждого, если вы хотите вернуть логическое значение на основе некоторого условия.

...