web-dev-qa-db-ja.com

どうやってグルーヴィーなクロージャーから戻ってその実行を停止するのですか?

ループでbreakステートメントを使用する場合と同様に、クロージャーから戻りたいと思います。

例えば:

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

上記のifステートメントでは、リスト全体の反復を停止し、不要な反復を避けるためにクロージャーを残したいと思います。

クロージャ内で例外がスローされて外部でキャッチされるソリューションを見てきましたが、そのソリューションはあまり好きではありません。

この種のアルゴリズムを回避するようにコードを変更する以外に、これに対する解決策はありますか?

36
Craig

それぞれの代わりにfindを使用したいと思います(少なくとも指定された例では)。クロージャーは直接ブレークをサポートしていません。

カバーの下では、groovyは実際にはfindにもクロージャーを使用せず、forループを使用します。

または、条件付きテストクロージャーと、一致が見つかった場合に呼び出す別のクロージャーを使用する独自の拡張バージョンのfind/eachイテレーターを作成して、一致した場合にブレークするようにすることもできます。

次に例を示します。

 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" 
} 
 
 //「printing on bar」
27
Ted Naleid

あなたは間違ったレベルの抽象化に取り組んでいると思います。 .eachブロックは、それが言っていることを正確に実行します。それは、各要素に対して一度クロージャを実行します。代わりにおそらく必要なのは、List.indexOfで正しいspecificElementを見つけ、その上で必要な作業を行います。

5
John Feminella

特定の要素が見つかるまですべての要素を処理したい場合は、次のようにすることもできます。

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

これは、どのような「ブレーク条件」でも使用できます。これを使用して、コレクションの最初のn個の要素を返すことで処理しました

counter++ >= n

閉鎖の終わりに。

3
Machisuji

私がgroovyを理解しているように、これらの種類のループをショートカットする方法は、ユーザー定義の例外をスローすることです。構文がどうなるかわかりませんが(grorovyプログラマーではありません)、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"; }     
1
paulmurray

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
1
David Tonhofer

今日、私は各クロージャーで作業しているときに同様の問題に直面しました。自分の状態で実行の流れを壊したかったのですが、できませんでした。

Groovyで行う最も簡単な方法は、何らかの条件に基づいてブール値を返したい場合に、リストの代わりにany()をリストで使用することです。

0
Tarun Sapra