倭マン's BLOG

くだらない日々の日記書いてます。 たまにプログラミング関連の記事書いてます。 書いてます。

クロージャ実行あれこれ

なんか今更な記事ですが、Groovy の Closure オブジェクトを実行する方法をあれこれと試行。

Closure クラスに定義されている実行用のメソッドには以下のようなものがあります:

    Object call()
    Object call(Object arg)
    Object call(Object... args)
    void run()

また、これらを用いずに「()」を使って実行することもできます。

実行の基本


まずは簡単なクロージャと基本的な実行:

def c = { println 'Hello, closure.' }

c()    // Hello, closure.
c.call()    // Hello, closure.

Groovy コードでは Closure オブジェクト直後に「()」を書けば実行できます。 これは、call() メソッドの呼び出しと同じになります。 Java コード内では最初の記法は使えないので、call() メソッドを明示的に呼ぶ必要があります。

引数の個数いろいろ


次は引数を渡してクロージャを実行する方法。 call() メソッドには、引数ナシ、引数1つ、可変長引数の3種類があり、渡したい引数の個数によって適当なものを呼び出すだけです。 「()」を使った呼び出しは、間に引数を挟んで渡せば対応する引数の call() メソッドが呼ばれます。 ちょっと注意が必要なのは、任意個数の引数で実行するコードがコンパイルを通りますが、実行時に例外が投げられる可能性があることでしょうか。

ちなみに、クロージャの引数を明示する場合は「{」と「->」の間に書きます:

{ 《引数のシグニチャ》 -> ... }

引数を明示しないクロージャ

引数を明示しない場合は、呼び出しの際の引数は0または1個が許されます:

def c = { println 'Hello, closure.' }

c()    // Hello, closure.
c('a')    // Hello, closure.
c('a', 'b')    //***** groovy.lang.MissingMethodException

引数1つ

1つの引数を明示する場合は、引数を明示しない場合と同じです:

def c = { arg -> println "Hello, $arg." }

c()    // Hello, null.
c('Groovy')    // Hello, Groovy.
c('a', 'b')    //***** groovy.lang.MissingMethodException

こちらも0または1個の引数が許されます。

引数2つ

引数2つを明示する場合は、2つの引数のみが許されれます:

def c = { arg0, arg1 -> println "$arg0, $arg1." }

c()    //*****  groovy.lang.MissingMethodException
c('Groovy')    //***** groovy.lang.MissingMethodException
c('Hello', 'Groovy')    // Hello, Groovy.

3個以上の場合も同じです。

引数0個

引数をとらないことを明示することもできます。 この場合は0個の引数のみが許されます:

def c = { -> println 'Hello, closure.' }

c()    //  Hello, closure.
c('Groovy')    //***** groovy.lang.MissingMethodException
c('Hello', 'Groovy')    //***** groovy.lang.MissingMethodException

引数を1つ渡しているときも例外が投げられます。

it 変数


引数を明示しない場合は1つの引数を明示する場合と同じなので、引数が1つ渡されたときでも動作しますが、この引数を使いたい場合は暗黙の変数 itを参照します:

def c = { println "Hello, $it." }

c('Groovy')    // Hello, Groovy.
c()    // Hello, null.

引数を渡さなかった場合は、it には null がセットされます。 ちなみに「引数1つ」の場合にサラッと書いてますが、引数1つを明示して引数を渡さなかった場合もその引数には null がセットされます:

def c = { arg -> println "Hello, $arg." }
c()    // Hello, null.

引数のデフォルト値


クロージャの引数には(メソッドと同様に)デフォルト値を設定することができます。 記法は

{ arg = 《デフォルト値》 -> ... }

といった具合です。 具体的に実行すると以下のようになります:

def c = { to = 'closure' -> println "Hello, $to." }

c()    // Hello, closure.
c('Groovy')    // Hello, Groovy.
c('Groovy', 'Java')    //***** groovy.lang.MissingMethodException

もちろん、引数の個数を増やして各引数にデフォルト値を設定することもできます。 やってみましょう:

def c = { greet = 'Hello', to = 'closure' -> println "$greet, $to." }

c()    // Hello, closure.
c('こんにちは')    // こんにちは, closure.
c('こんにちは', 'Groovy')    // こんにちは, Groovy.

引数0個と2個の場合は問題ないでしょう。 引数が1個の場合は前から順に引数が割り当てられるようです。 ちなみに、引数の型を明示しても適当な呼び出しに変換してくれたりはしないもよう:

def c = { String to = 'closure', int n = 1 -> n.times{ print "Hello, " }; println(to) }

c()    // Hello, closure
c('Groovy')    // Hello, Groovy
c('Groovy', 2)    // Hello, Hello, Groovy
c(2)    //***** groovy.lang.MissingMethodException

最後の「2」を渡した場合、1つ目の引数 to はデフォルト値にして2つ目の引数 n に2 をセットする・・・なんてことはしてくれないようですね。

ところで、引数を明示しないクロージャは以下のクロージャと同じなようですね:

{ it = null -> ... }

Java のスレッドプログラミングとの関連


Closure クラスは java.lang.Runnable インターフェースを実装しているので、別スレッドで実行するのも簡単です。 run() メソッドは call() メソッドと同じです(たぶん):

def c = {
    Thread.sleep(3000)
    println 'Hello, closure.'
}

Thread.start(c)    // Hello, closure. (async)

Closure クラスは、java.util.concurrent.Callable インターフェースも実装しているので、java.util.concurrent パッケージを使った並行プログラミングでも簡単に使えます:

import java.util.concurrent.Executors

def c = {
    Thread.sleep(3000)
    println 'Hello, closure.'
}

def exec = Executors.newCachedThreadPool()
exec.submit(c)    // Hello, closure. (async)
exec.shutdown()

追記


プログラミングGROOVY』を開いてみたら全部書いてましたね(^ ^;) まぁ、次回以降に書く予定の Closure クラスのメソッド使用例の記事の導入ってことで。

プログラミングGROOVY

プログラミングGROOVY