Groovy クロージャーで「続行」をシミュレートするための最適なパターン

StackOverflow https://stackoverflow.com/questions/205660

  •  03-07-2019
  •  | 
  •  

質問

Groovyは対応していないようです break そして continue クロージャ内から。これをシミュレートする最良の方法は何ですか?

revs.eachLine { line -> 
    if (line ==~ /-{28}/) {
         // continue to next line...
    }
}
役に立ちましたか?

解決

サポートできるのは、中断ではなく、きれいに継続することだけです。特に eachLine や each のようなものではそうです。ブレークをサポートできないのは、これらのメソッドの評価方法に関係しており、メソッドに伝達できるループが終了していないことは考慮されていません。サポートを継続する方法は次のとおりです --

最良のアプローチ (結果の値が必要ないと仮定します)。

revs.eachLine { line -> 
    if (line ==~ /-{28}/) {
        return // returns from the closure
    }
}

サンプルが本当に単純であれば、これは読みやすさの点で優れています。

revs.eachLine { line -> 
    if (!(line ==~ /-{28}/)) {
        // do what you would normally do
    }
}

別のオプションでは、Continue が通常行うことをバイトコード レベルでシミュレートします。

revs.eachLine { line -> 
    while (true) {
        if (line ==~ /-{28}/) {
            break
        }
        // rest of normal code
        break
    }

}

ブレークをサポートする考えられる方法の 1 つは、例外を使用することです。

try {
    revs.eachLine { line -> 
        if (line ==~ /-{28}/) {
            throw new Exception("Break")
        }
    }
} catch (Exception e) { } // just drop the exception

特に、NumberFormatExceptions や IOExceptions など、実際の例外をスローする可能性のある他の処理がそのクラスで実行されている場合は、他の実際の例外のマスクを避けるために、カスタム例外タイプを使用することをお勧めします。

他のヒント

クロージャーは、ループ/反復構造ではないため、中断または続行できません。代わりに、反復ロジックの処理/解釈/処理に使用されるツールです。次のように処理せずにクロージャから戻るだけで、指定された反復を無視できます。

revs.eachLine { line -> 
    if (line ==~ /-{28}/) {
            return
    }

}

ブレークのサポートはクロージャーレベルでは発生しませんが、代わりにクロージャーを受け入れたメソッド呼び出しのセマンティクスによって暗示されます。つまり、「各」を呼び出す代わりにコレクション全体を処理することを目的としたコレクションのようなものでは、特定の条件が満たされるまで処理するfindを呼び出す必要があります。クロージャから抜け出す必要があると感じる(ほとんどの場合)ほとんどの場合、本当にやりたいことは、反復中に特定の条件を見つけることです。これにより、findメソッドが論理的なニーズだけでなく、意図にも一致します。残念なことに、一部のAPIにはfindメソッドのサポートがありません...ファイルなど。言語にbreak / continueを含めるかどうかの議論に費やされたすべての時間は、これらの無視された領域にfindメソッドを追加することに費やされた可能性があります。 firstDirMatching(Closure c)やfindLineMatching(Closure c)のようなものは大いに役立ち、「なぜ私は…から脱出できないのか」の99 +%に答えます。メーリングリストに表示される質問。とはいえ、これらのメソッドをMetaClassまたはCategories経由で自分で追加するのは簡単です。

class FileSupport {
   public static String findLineMatching(File f, Closure c) {
      f.withInputStream {
         def r = new BufferedReader(new InputStreamReader(it))
         for(def l = r.readLine(); null!=l; l = r.readLine())
             if(c.call(l)) return l
         return null
      }
   }
}

using(FileSupport) { new File("/home/me/some.txt").findLineMatching { line ==~ /-{28}/ }

例外やその他の魔法を含む他のハックは機能する場合がありますが、状況によっては余分なオーバーヘッドが発生し、他の状況では読みやすさが畳み込まれます。本当の答えは、コードを見て、代わりに本当に繰り返し処理するのか検索するのかを尋ねることです。

Javaで静的なExceptionオブジェクトを事前に作成してから、クロージャー内から(静的な)例外をスローする場合、実行時のコストは最小限です。実際のコストは、例外をスローするのではなく、例外を作成するときに発生します。 Martin Odersky(Scalaの発明者)によると、多くのJVMは実際に単一ジャンプへのthrow命令を最適化できます。

これはブレークをシミュレートするために使用できます:

final static BREAK = new Exception();
//...
try {
  ... { throw BREAK; }
} catch (Exception ex) { /* ignored */ }

return を使用して continue し、 any を使用して break します。

ファイルの内容:

1
2
----------------------------
3
4
5

Groovyコード:

new FileReader('myfile.txt').any { line ->
    if (line =~ /-+/)
        return // continue

    println line

    if (line == "3")
        true // break
}

出力:

1
2
3

この場合、おそらく find()メソッドを考える必要があります。クロージャが最初に渡された後に停止し、trueを返します。

rx-java を使用すると、イテラブルをオブザーバブルに変換できます。

その後、 continue filter に、 break takeWhile

に置き換えることができます

例を次に示します。

import rx.Observable

Observable.from(1..100000000000000000)
          .filter { it % 2 != 1} 
          .takeWhile { it<10 } 
          .forEach {println it}
ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top