Comment peut-on revenir d'une fermeture groovy et arrêter son exécution?

Je voudrais revenir d'une fermeture, comme on pourrait si l'aide d'une instruction break dans une boucle.

Par exemple:

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

Dans l'instruction if ci-dessus, je voudrais arrêter d'itérer dans la liste et laisser la fermeture pour éviter les itérations inutiles.

J'ai vu une solution où une exception est lancée dans la fermeture et prise à l'extérieur, mais je n'aime pas trop Cette solution.

Y a-t-il des solutions à cela, autres que de changer le code pour éviter ce genre d'algorithme?

31
demandé sur John Topley 2009-04-19 19:49:41

5 réponses

Je pense que vous voulez utiliser find au lieu de chacun (au moins pour l'exemple spécifié). Les fermetures ne supportent pas directement la rupture.

Sous les couvertures, groovy n'utilise pas non plus de fermeture pour find, il utilise une boucle for.

Alternativement, vous pouvez écrire votre propre version améliorée de find / chaque itérateur qui prend une fermeture de test conditionnelle, et une autre fermeture à appeler si une correspondance est trouvée, la faisant casser si une correspondance est rencontrée.

Voici un exemple:

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"
26
répondu Ted Naleid 2009-04-20 00:35:46

, je pense que vous travaillez sur le mauvais niveau d'abstraction. Le bloc .each fait exactement ce qu'il dit: il exécute la fermeture une fois pour chaque élément. Ce que vous voulez probablement à la place l'utilisation de List.indexOf pour trouver le bon specificElement, puis faire le travail que vous devez faire sur elle.

5
répondu John Feminella 2009-04-19 15:55:10

Si vous voulez traiter tous les éléments jusqu'à ce qu'un élément spécifique soit trouvé, vous pouvez également faire quelque chose comme ceci:

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

Bien que vous puissiez l'utiliser avec n'importe quel type de "condition de rupture". Je viens de l'utiliser pour traiter les N premiers éléments d'une collection en retournant

counter++ >= n

À la fin de la fermeture.

4
répondu Machisuji 2009-12-13 14:18:29

Si je comprends bien groovy, la façon de raccourcir ces types de boucles serait de lancer une exception définie par l'utilisateur. Je ne sais pas quelle serait la syntaxe (pas un programmeur grrovy), mais groovy fonctionne sur la JVM donc ce serait quelque chose comme:

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"; }     
1
répondu paulmurray 2010-08-13 02:49:54

Après la réponse de paulmurray, Je n'étais pas sûr de ce qui se passerait avec une Exception lancée de l'intérieur d'une fermeture, alors j'ai fouetté un cas de test JUnit facile à penser:

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

La sortie de ce qui précède est:

a
b
c
Found c

Mais rappelez-vous que "Il ne faut pas utiliser D'Exceptions pour le contrôle de flux" , voir en particulier cette question de débordement de pile: Pourquoi ne pas utiliser des exceptions comme flux régulier de contrôle?

Donc la solution CI-DESSUS est moins qu'idéale dans tout cas. Il suffit d'utiliser:

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

La sortie de ce qui précède est également:

a
b
c
Found c
1
répondu David Tonhofer 2017-05-23 11:53:07