groovy - que - ¿Cómo puede uno regresar de un cierre maravilloso y detener su ejecución?
how to put hashtags on youtube videos (5)
Como entiendo groovy, la forma de atajar este tipo de bucles sería lanzar una excepción definida por el usuario. No sé cuál sería la sintaxis (no es un programador grosero), pero Groovy se ejecuta en la JVM por lo que sería algo así como:
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"; }
Me gustaría regresar de un cierre, como lo haría si usara una sentencia break en un ciclo.
Por ejemplo:
largeListOfElements.each{ element->
if(element == specificElement){
// do some work
return // but this will only leave this iteration and start the next
}
}
En la declaración if anterior, me gustaría dejar de iterar a través de la lista y dejar el cierre para evitar iteraciones innecesarias.
He visto una solución donde se arroja una excepción dentro del cierre y se atrapa afuera, pero no me gusta esa solución.
¿Hay alguna solución a esto, además de cambiar el código para evitar este tipo de algoritmo?
Creo que estás trabajando en el nivel incorrecto de abstracción. El bloque .each
hace exactamente lo que dice: ejecuta el cierre una vez para cada elemento. En su lugar, lo que probablemente desee es usar List.indexOf
para encontrar el elemento specificElement
correcto y luego haga el trabajo que necesita hacer en él.
Creo que quieres usar find en lugar de cada uno (al menos para el ejemplo especificado). Los cierres no son compatibles directamente con el descanso.
Debajo de las sábanas, Groovy en realidad no usa un cierre para encontrarlo, sino que usa un bucle for.
Alternativamente, puede escribir su propia versión mejorada de find / cada iterador que toma un cierre de prueba condicional, y otro cierre para llamar si se encuentra una coincidencia, rompiéndola si se cumple una coincidencia.
Aquí hay un ejemplo:
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"
Después de la respuesta de paulmurray, no estaba seguro de qué sucedería con una Excepción lanzada desde dentro de un cierre, por lo que preparé un caso de prueba JUnit que es fácil de considerar:
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)
}
}
}
El resultado de lo anterior es:
a
b
c
Found c
Pero recuerde que "NO se deben usar excepciones para el control de flujo" , consulte en particular esta pregunta sobre el desbordamiento de pila: ¿Por qué no usar excepciones como flujo de control regular?
Entonces la solución anterior es menos que ideal en cualquier caso. Solo usa:
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}")
}
}
El resultado de lo anterior también es:
a
b
c
Found c
Si desea procesar todos los elementos hasta que se encuentre uno específico, también podría hacer algo como esto:
largeListOfElements.find { element ->
// do some work
element == specificElement
}
Aunque puedes usar esto con cualquier tipo de "condición de ruptura". Acabo de usar esto para procesar los primeros n elementos de una colección volviendo
counter++ >= n
al final del cierre.