今回はクロージャを込み入った方法で使う際に意識しておくべき3つのオブジェクト
- this (thisObject)
- owner
- delegate
とそれに関するメソッドを見ていきます。 まぁ、この記事読んだところで、結局よぉ分からんってオチになりそうだけど。
this, owner, and delegate
とりあえず Groovy のドキュメント「Groovy - Closures」を参照してみると
- this : as in Java, this refers to the instance of the enclosing class where a Closure is defined
- owner : the enclosing object (this or a surrounding Closure)
- delegate : by default the same as owner, but changeable for example in a builder or ExpandoMetaClass
と書かれてます。 this はクロージャの定義を含んでいるクラスのインスタンス、owner はクロージャの定義を含んでいるオブジェクト(this オブジェクトまたは Closure オブジェクト)、delegate はデフォルトでは owner と同じだけど変更可能、といったところでしょうか。 これら3つのオブジェクトは全て同じオブジェクトがセットされていることも多いようです:
- delegate を別途セットしなければ delegate と owner は同じ
- クロージャの中でクロージャを定義すると、this と owner は異なるオブジェクトがセットされる
まぁ、サンプル・コードを見た方がはやいと思いますが、その前にこれらのオブジェクトを取得するメソッドを見ておきましょう。
各種オブジェクトを取得するメソッド
Object getDelegate()
void setDelegate(Object delegate)
Object getOwner()
Object getThisObject()
getThisObject() メソッドは Javadoc に何も書かれてませんが、「this」で参照されるオブジェクトでしょう。 delegate には getter/setter メソッドが定義されてますが、owner, this には setter メソッドがないので変更不可です*1。
サンプル・コード from 『Groovy in Action』
んでは、サンプル・コード。 まずは『Groovyイン・アクション』からサンプルを拝借(ちょっと改変):
class Mother{ int field = 1 int foo(){ return 2 } Closure birth(param){ def local = 3 return { caller -> [ this, // Mother object field, // field of Mother ( == 1 ) foo(), // method of Mother ( return 2 ) local, // local variable ( == 3 ) param, // parameter of birth method caller, // parameter of Closure owner // Mother object ( == this in this case) ] } } } Mother julia = new Mother() c = julia.birth(4) // param == 4 context = c(this) // caller is instance of Script assert context[0].class == Mother assert context[1..4] == [1, 2, 3, 4] assert context[5] instanceof Script assert context[6] instanceof Mother
クロージャの定義が Mother クラスの中にあるので、this も owner も Mother オブジェクトがセットされます(julia)。 このサンプルでは delegate が使われておらず、また owner と this が同じなので、3つのオブジェクトの違いがよくわかりませんな。 次はこんなサンプル:
def c = { println '// in closure "c"' println 'delegate : ' + delegate.class println 'owner : ' + owner.class println 'this : ' + this.class println() return { println '// in result closure returned by "c"' println 'delegate : ' + delegate.class println 'owner : ' + owner.class println 'this : ' + this.class } } c()()
クロージャの中でクロージャを定義しています。 変数 c に代入されているクロージャ(外側のクロージャ)は、評価されるとクロージャ(内側のクロージャ)を返します。 このスクリプトを実行すると、出力結果は以下のようになります(数字部分は異なる可能性アリ):
// in closure "c" delegate : class ConsoleScript5 owner : class ConsoleScript5 this : class ConsoleScript5 // in result closure returned by "c" delegate : class ConsoleScript5$_run_closure1 owner : class ConsoleScript5$_run_closure1 this : class ConsoleScript5
最初の出力パートから、外側のクロージャには3つのオブジェクトとして同じもの(Script オブジェクト)がセットされていることが分かります。 一方、2つ目の出力パートから、内側のクロージャには delegate と owner として外側のクロージャが、this として Script オブジェクトがセットされてます。
関連メソッドもう少し
delegate, owner, this の3つのオブジェクトに関連する Closure クラスのメソッドは他にも幾つかあるので、それらを見ていきましょう。 Closure クラスに定義されている定数にも関係するものがあるので、そちらも載せておきます:
Closure rehydrate(Object delegate, Object owner, Object thisObject) Closure dehydrate() int getResolveStrategy() void setResolveStrategy(int resolveStrategy) // Constants static int DELEGATE_FIRST static int DELEGATE_ONLY static int OWNER_FIRST // default static int OWNER_ONLY static int TO_SELF
- rehydrate() メソッドは delegate, owner, thisObject に引数のオブジェクトをセットしたクロージャのコピーを返します。
- dehydrate() メソッドは3つのオブジェクトすべてに null をセットしたクロージャのコピーを返します。
- getResolveStrategy() / setResolveStrategy() メソッドは、クロージャ内の変数の解決方法を取得・設定します。 設定可能な int 値は上記の5つ定数です。 各定数の意味はドキュメント参照。 このどれが設定されていても、ローカル変数での解決が優先されるので注意。
サンプル・コード
では、上記のメソッド(の一部)を使ったサンプル・コードを見ていきましょう。 まずは準備として、以下のような Person クラスを定義しておきます:
@groovy.transform.Canonical class Person{ String name int age }
さて、この Person クラスのインスタンスに対して、クロージャを使ってプロパティをセットしてやります。 Groovy コードなら、Closure の API を使わずに with() メソッドを用いて
def c = { name = 'Waman' age = 100 } def p = new Person() p.with(c) // クロージャでプロパティのセット println p // Person(Waman, 100)
とすれば OK なんですが、これを Java コードなどでもできるように Closure の API を使って行うのが目標。
delegate を設定(失敗)
まずは delegate を設定する方法。
def c = { name = 'Waman'; age = 100 } def p = new Person() c.delegate = p c() println p // Person(null, 0)
でもこれは失敗。 うーむ、owner が Script だからまずいのかな?
rehydrate() メソッドで owner を設定
デフォルトの resolveStrategy は OWNER_FIRST なので owner に Person オブジェクトをセットしておけばうまくいくようです:
def c = { name = 'Waman'; age = 100 } def p = new Person() d = c.rehydrate(c.delegate, p, c.thisObject) d() println p // Person(Waman, 100)
でも、rehydrate() でコピー作るのはちょっと大仰な気がしますねぇ。 どの程度コストがかかるのか知りませんが。
resolveStrategy を設定
次は delegate の設定と共に、resolveStragegy の値を DELEGATE_FIRST に設定してあげましょう:
def c = { name = 'Waman'; age = 100 } def p = new Person() c.resolveStrategy = Closure.DELEGATE_FIRST c.delegate = p c() println p // Person(Waman, 100)
これが一番無難そうなコードでしょうかね。 with() が使えるならそれに越したことはないですが。
- 作者: 関谷和愛,上原潤二,須江信洋,中野靖治
- 出版社/メーカー: 技術評論社
- 発売日: 2011/07/06
- メディア: 単行本(ソフトカバー)
- 購入: 6人 クリック: 392回
- この商品を含むブログ (152件) を見る
- 作者: Dierk Konig,Andrew Glover,Paul King,Guillaume Laforge,Jon Skeet,杉浦孝,櫻井正樹,須江信洋,関谷和愛,佐野徹郎,寺沢尚史
- 出版社/メーカー: 毎日コミュニケーションズ
- 発売日: 2008/09/27
- メディア: 単行本(ソフトカバー)
- 購入: 5人 クリック: 146回
- この商品を含むブログ (121件) を見る
*1:後で見る rehydrate() メソッドを使えば owner, thisに任意のオブジェクトを設定したクロージャを生成することができます。