#参考
Closures(公式)
#概要
Groovyのクロージャには、this、owner、delegateという暗黙的に利用できる少し変わった役割をする変数があります。
Groovyの公式リファレンスの内容を意訳すると以下のようになります。
|名前 |役割 |
|---|---|---|
|this |Javaのthisと同じです。自分が定義された場所のインスタンスを保持しています。 |
|owner |thisか、自分を囲んでいるクロージャになります。 |
|delegate |ownerと同じです。が、意図的に変更することができます。(ExpandoMetaClassやbuilerなど) |
コレだけだとはっきり言って全く理解できなかったので、実際にソースを書いてみました。
#サンプルソース
class Test {
// 普通のクロージャを定義
Closure a = {
println "普通のクロージャ"
println " this: ${this.class.name}"
println " owner: ${owner.class.name}"
println " delegate: ${delegate.class.name}"
// クロージャの中にさらにクロージャを定義。
// aクロージャが実行されたら自分自身も実行されるよう最後に()を付けてる
{->
println "クロージャの中のクロージャ"
println " this: ${this.class.name}"
println " owner: ${owner.class.name}"
println " delegate: ${delegate.class.name}"
}()
}
}
def test = new Test()
test.a()
実行結果は以下のようになります。
普通のクロージャ
this: Test
owner: Test
delegate: Test
クロージャの中のクロージャ
this: Test
owner: Test$_closure1
delegate: Test$_closure1
thisについては変化なしです。
ownerとdelegateが普通のクロージャと、クロージャの中のクロージャでは異なっています。
クラス名に$マークが付いているものは、左側のクラスの内部クラスという意味にります。
上記の実行結果だとTest$_closure1
はTestクラス
の中の_closure1
という内部クラス、という意味になります。Groovyはクロージャを内部クラスとして実現しているんですね。
ちょっと分かりづらいのですが、今回の_closure1
は、変数aに格納されている大本のクロージャのことです。
クロージャの中のクロージャ自身はTest$_closure1_closure2
という名前になっていました。
($以降の内部クラス名は勝手に一意なものが割り振られるので変動します。)
...言葉にするとやっぱり分かりづらいですね。
thisはクロージャが生成された大本のクラスです。Javaでいうthisと一緒なのでコレは特に難しくないと思います。
ownerが分かりづらいですが、普通にクラスのプロパティとか、メソッド内にクロージャを宣言した場合、thisとownerは同じになります。
ownerの値が変化する(thisと異なる)のは、クロージャの中にクロージャを宣言した時になります。(多分。。。)
そしてその値は自分の直近の親クロージャになります。
delegateについてはデフォルトではownerと同じです。
じゃあなんのためにあるの?というのは以下のサンプルで。
#応用
クロージャの定義は難しいですが、少なくともGroovyのクロージャは自分が定義された場所で参照できる変数は別の場所に自分(クロージャ自身)が渡されて実行されたとしても参照できます。
そして、thisとownerは不変ですが、delegateについては手動で変更することができます。
これらの性質を利用すると以下のようなことが実現できます。
class Test2 {
String abc = "Test2"
def exec(Closure a) {
a()
}
}
class Hoge {
static main() {
String abc = "ConsoleScript"
def test2 = new Test2()
// 凄い普通。abcという変数の中身を返すクロージャ。
// cls1が”実行されるときに"Hoge#main()で宣言している変数abcを参照するので、言うまでもなく変数abcを宣言しておかないとエラーになる。
Closure cls1 = { -> abc }
assert test2.exec( cls1 ) == "ConsoleScript"
// コレが応用バージョン
// delegateを明示的に指定して、さらにクロージャ自身のdelegateにtest2インスタンスを
// 指定することで、クロージャを渡した先(test2)の変数abcが参照されるようになる。
// "Hoge#main()で宣言している変数abc"は使用しないので、このパターンだけで良ければHoge#main()の中の変数abcは削除しても大丈夫。
Closure cls2 = { -> delegate.abc }
cls2.delegate = test2
assert test2.exec( cls2 ) == "Test2"
}
}
Hoge.main()
#まとめ
いつ使うかと言われれば微妙な内容なので、今までちゃんと調べることをスルーしてきましたが、ちゃんと理解しよう!と思ってまとめました。
ExpandoMetaClassという、メソッドを動的に既存クラスに追加する機能を利用するときにdelegateが必要らしいです。
ExpandoMetaClassについてはまた調べてQiitaに投稿したいと思います。