32
27

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

[Groovy]クロージャのthis、owner、delegateについて

Last updated at Posted at 2014-09-04

#参考
Closures(公式)

#概要
Groovyのクロージャには、thisownerdelegateという暗黙的に利用できる少し変わった役割をする変数があります。
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$_closure1Testクラスの中の_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に投稿したいと思います。

32
27
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
32
27

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?