倭マン's BLOG

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

あんたに GDK の何が分かるっていうの!? (Object 編2) プロパティ、リフレクション

今回は GDK が Object クラスに追加しているプロパティ、リフレクション関連のメソッド。 (正式な仕様はともかく)Groovy でのプロパティとは getter/setter メソッド*1で値の取得・設定ができるメンバという感じでしょうか。 Groovy ではフィールドを定義すると自動的に getter/setter メソッドが追加される*2ので、フィールドもプロパティになります。 これを踏まえて、今回見ていくメソッドは以下のもの:

// プロパティ
Map getProperties()
Object getAt(String name)
void putAt(String name, Object value)

// メソッド名によるメソッド実行
Object invokeMethod(String name, Object args)

Object identity(Closure closure)
Object with(Closure closure)

各メソッドをもう少し詳しく見てみるとこんな感じ:

メソッド 返り値 since 説明
getProperties() Map 1.0 クラスに定義されている全プロパティを Map として取得
getAt(String) Object 1.0 「obj['prop']」形式でプロパティの値を取得する
putAt(String, Object) void 1.0 「obj['prop'] = value」形式でプロパティの値を設定する
invokeMethod(String, Object) Object 1.0 メソッド名を指定してメソッドを実行する
identity(Closure) Object 1.0 with() と同じ
with(Closure) Object 1.5.0 with() を呼び出されたオブジェクトを delegate 対象としてクロージャを実行する

identity() メソッドは with() メソッドと同じなので以降では扱いません。 以下で簡単なサンプルコードを見ていきましょう。

プロパティ


まずはプロパティに関するメソッド。 以下のように Person クラスが定義されているとしましょう:

class Person{
    String name
    int age
}

このとき、以下のようにプロパティにアクセスすることができます:

def waman = new Person()

// putAt() によるプロパティ値の設定
waman['name'] = '倭マン'
waman['age'] = 100

// getAt() によるプロパティ値の取得
assert waman['name'] == '倭マン'
assert waman['age'] == 100

// プロパティを Map オブジェクトとして取得
waman.getProperties().each{ key, value ->    // 各プロパティを列挙
    println "$key : $value"
}
// class : class Person
// age : 100
// name : 倭マン
// と表示(順序は違うかも)

どのクラスにも「class」プロパティは常に存在します(getClass() メソッドがあるので)。 ちなみに、final フィールド、getter のみのプロパティなども getAt() や getProperties() で取得できます:

class Person{

    String name
    int age
    final String state = 'alive'

    String getSex(){
        return 'male'
    }

    boolean isMale(){
        return sex == 'male'
    }
}

new Person(name:'倭マン', age:100).getProperties().each{ key, value ->
    println "$key : $value"
}
// class : class Person
// male : true
// sex : male
// age : 100
// state : alive
// name : 倭マン
// と表示

ちなみに properties という名前のプロパティはありませんが、あたかもあるかのようにプロパティを参照することができます。 つまり、以下の3つの呼び出しは同じです:

waman.getProperties()
waman['properties']
waman.properties

最後のものは今回扱うメソッドと関係ありませんが、フィールドアクセスのように記述できるので一番使いやすいかと。 実際には1つ目の getter によるアクセスをしているのと同じですが。 この記法による挙動を変更したい場合は、getAt() / putAt() ではなく

Object getProperty(String name)
void setProperty(String name, Object value)
Object propertyMissing(String name, Object value)

というメソッドを使います。 詳しくは『プログラミングGROOVY』参照。

メソッドの実行


次はメソッドをメソッド名で実行する invokeMethod() メソッド。 Person クラスにいくつかメソッドを定義しておきましう:

class Person{

    String name
    int age

    // 引数なし
    def introduce(){
        println "拙者の名前は${name}でござる。 齢${age}で候。"
    }

    // 引数1つ
    def greeting(Person someone){
        println "こんにちは、${someone.name}さん。"
    }

    // 引数2つ
    def greeting(String name, int age){
        greeting(new Person(name:name, age:age))
    }
}

invokeMethod() メソッドはメソッド名以外に引数を渡す場合に1つのオブジェクトをとるだけなので、ちょっと注意が必要かな:

def waman = new Person(name:'倭マン', age:100)

// 引数なし -> null
waman.invokeMethod('introduce', null)

// 引数1つ -> オブジェクト
waman.invokeMethod('greeting', new Person(name:'サムワン', age:99))

// 引数2つ(複数) -> リスト(配列でもよさそう)
waman.invokeMethod('greeting', ['そもさん', 1000])

引数が2つ(以上)の場合、第2引数は List や配列なら大丈夫そうです。 ただし、後日やる printf() メソッドのように invokeMethod('greeting', 'そもさん', 1000) という風にはできませんでした。 第2引数が Object の配列だったらできるのかな? ちなみに、メソッドがない場合の挙動を変更したい場合、propertyMissin() メソッドと同様の

Object methodMissing(String name, Object args)

メソッドを定義することで好きな処理を行えます。

with() メソッド


with() メソッドは、クロージャの delegate 対象、つまりプロパティアクセスやメソッド実行を行われるオブジェクトを指定して実行するメソッドです。 最初は使い勝手が分かりにくいメソッドですが、使えるようになると何かと便利。

def waman = new Person()

waman.with{
    name = 'Waman'    // waman オブジェクトに対して name プロパティの値が設定される
    age = 50
    introduce()    // waman オブジェクトに対して introduce() メソッドが実行される
}

assert waman.name == 'Waman'
assert waman.age == 50

waman.with{ ... } の部分で waman オブジェクトに対してプロパティアクセスやメソッド実行が行われます。

今回扱ったメソッドは Groovy のコードを見易くしてくれ、またメタプログラミングの入門にもなると思うので、Groovy を使うなら把握しておきたいメソッドばかり。 次回はコンテナ・メソッドの予定。

プログラミングGROOVY

プログラミングGROOVY

*1:getter だけでもよさそう。 boolean 値の isXxxx() 形式でも OK。

*2:フィールドに final 宣言を付けておくと、getter のみが追加される