Как вернуться из закрытия groovy и остановить его выполнение?
Я хотел бы вернуться из закрытия, как если бы вы использовали оператор break в цикле.
Например:
largeListOfElements.each{ element->
if(element == specificElement){
// do some work
return // but this will only leave this iteration and start the next
}
}
В приведенном выше операторе if я хотел бы остановить повторение в списке и оставить закрытие, чтобы избежать ненужных итераций.
Я видел решение, в котором исключение выбрасывается в закрытие и выловлено на улицу, но я не слишком люблю это решение.
Есть ли какие-либо решения для этого, кроме изменения кода, чтобы избежать такого алгоритма?
Ответы
Ответ 1
Я думаю, вы хотите использовать find вместо каждого (по крайней мере, для указанного примера). Закрытие не поддерживает прямой перерыв.
Под обложками groovy фактически не использует закрытие для find, он использует цикл for.
В качестве альтернативы вы можете написать собственную расширенную версию find/each iterator, которая принимает закрытие условного теста, и другое замыкание для вызова, если найдено совпадение, если оно будет разорвано, если соответствие выполнено.
Вот пример:
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"
Ответ 2
Если вы хотите обработать все элементы до тех пор, пока не будет найден какой-либо конкретный объект, вы также можете сделать что-то вроде этого:
largeListOfElements.find { element ->
// do some work
element == specificElement
}
Хотя вы можете использовать это с любым "условием прерывания".
Я просто использовал это для обработки первых n элементов коллекции, возвращая
counter++ >= n
в конце замыкания.
Ответ 3
Я думаю, вы работаете над неправильным уровнем абстракции. Блок .each
выполняет именно то, что он говорит: он выполняет закрытие один раз для каждого элемента. Вместо этого вам нужно использовать List.indexOf
, чтобы найти правильный specificElement
, а затем выполнить работу, которую вам нужно сделать.
Ответ 4
Как я понимаю 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"; }
Ответ 5
После ответа paulmurray я не был уверен, что произойдет с Исключением, выброшенным из замыкания, поэтому я взломал тестовый пример 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