今さらかも知れませんが、Groovy でコンストラクタを実装する方法をあれこれ試してみました。 今回試したのは以下のような方法です:
- 基本
- アノテーションによる AST 変換
- メタプログラミング
- 実行時メタプログラミング
- マジック・パッケージ
では、それぞれを見ていきましょう。
基本
まずは基本。 デフォルト・コンストラクタとコンストラクタ定義は Java と同じ。 名前付き引数は Groovy 用です。
デフォルト・コンストラクタ、名前付き引数
Groovy でクラス定義にコンストラクタを書かなければ、Java と同じようにデフォルト・コンストラクタが自動生成されます。 デフォルト・コンストラクタは引数なしで特に何の処理もしない*1コンストラクタです。 また、Groovy 特有のコンストラクタとして名前付き引数のコンストラクタも自動生成されます。 このコンストラクタには名前をキーとする Map を渡します:
import groovy.transform.ToString @ToString class Person1{ String name int age } def a = new Person1() // デフォルト・コンストラクタ a.with{ name = 'Alberto'; age = 12 } println a // Person1(Alberto, 12) def b = new Person1(name:'Beryl', age:34) // 名前付き引数 println b // Person1(Beryl, 34)
コンストラクタ定義
次は Java のようにコンストラクタを定義した場合。 まぁ、Java となんら変わりません:
import groovy.transform.* @ToString class Person2{ String name int age Person2(String name, int age){ this.name = name this.age = age } } def c = new Person2('Chris', 56) println c // Person2(Chris, 56) //def c1 = new Person2() // 例外 //def c2 = new Person2(name:'Chris', age:56) // 例外
アノテーションによるAST 変換
次は Groovy 1.8 あたりから導入された、アノテーションによる AST 変換を使った、よくあるコンストラクタ実装の自動生成を行う方法。
の2つがコンストラクタを自動生成するアノテーションです。 どちらも groovy.transform パッケージに含まれています。
@InheritConstructors アノテーション
@Inheritconstructors アノテーションはスーパークラスのコンストラクタと同じ引数、同じ処理のコンストラクタを自動生成します:
import groovy.transform.* @InheritConstructors class Person3 extends Person2{} def d = new Person3('Debby', 78) println d // Person2(Debby, 78) // def d1 = new Person3() // 例外 // def d2 = new Persion3(name:'Debby', age:78) // 例外
- Person2 クラスは「基本. コンストラクタ定義」の箇所に出てきたクラスです。
- Person3 クラスにコンストラクタの定義はありませんが、デフォルト・コンストラクタなどは使えません。
- Person3 クラスに新たにコンストラクタを定義することも可能です。
@TupleConstructor アノテーション
@TupleConstructor アノテーションは、コンストラクタに渡された引数でプロパティ(フィールド)を順次初期化していくコンストラクタを生成します:
import groovy.transform.* @TupleConstructor @ToString class Person4{ String name int age } def e = new Person4('Ernesto', 90) // 引数によって順次プロパティを初期化 println e // Person4(Ernesto, 90) def f = new Person4() // 引数なしも OK f.with{ name = 'Florence'; age = 123 } println f // Person4(Florence, 123) def g = new Person4('Gordon') // 渡した引数の個数だけプロパティを初期化 g.age = 456 println g // Person4(Gordon, 456)
- Person4 は2つのプロパティ name, age を持つので、引数2つの場合は順に name, age を初期化します。
- 引数がプロパティの個数以下でも OK です。 引数なしでも可。 引数の個数がプロパティの個数より少ない場合は、引数の個数だけ順番にプロパティを初期化します。
メタプログラミング
次はメタクラスを使ったメタプログラミングによるコンストラクタの実装。
- 実行時メタプログラミング
- マジック・パッケージ
拡張モジュールを使ってコンストラクタを追加できないかなぁ?と思ったんですが、あんまりうまくいかなさそうだったのでマジック・パッケージを使った方法を試しました。
実行時メタプログラミング
まずは実行時にメタクラスをイジる実行時メタプログラミング。
import groovy.transform.* @ToString class Person{ String name int age } // メタプログラミング その1 Person.metaClass.constructor = { String name, int age -> new Person(name:name, age:age) } def h = new Person('Helene', 789) println h // Person(Helene, 789) // メタプログラミング その2 Person.metaClass{ constructor = { String name -> new Person(name:name, age:100) } } def i = new Person('Isaac') println i // Person(Isaac, 100)
- その1ではメタクラスの constructor プロパティに Person オブジェクトを返すクロージャをセットしています。
- その2でもやっていることは同じ。 Person クラスの static メソッド metaClass() を呼び出しているような書き方。 この書き方では、他にメソッドも追加したいといったときにキレイに書けます。
マジック・パッケージ
最後は、以前にメソッドを追加するのに試したマジック・パッケージを用いる方法。 コンパイルを行わないといけない方法ですが、実行時メタプログラミングの場合のようなメタクラスをイジるコードを呼び出す必要がありません。
まずは以下のような Person クラスが定義されているとしましょう:
package org.sample @groovy.transform.ToString class Persion{ String name int age }
このとき、以下のようなパッケージ、クラス名の MetaClass のサブクラスを作成します:
package groovy.runtime.metaclass.org.sample import org.sample.Person class PersonMetaClass extends DelegatingMetaClass{ PersonMetaClass(MetaClass delegate) { super(delegate) } @Override Object invokeConstructor(Object[] args) { if(args.size() == 1 && args[0] instanceof String){ return new Person(name:args[0], age:200) }else{ return super.invokeConstructor(args) } } }
- パッケージ名は groovy.runtime.metaclass の後に、コンストラクタを追加したいクラスのパッケージを続ける
- クラス名はコンストラクタを追加したいクラスの名前に 'MetaClass' を続ける
- コンストラクタの処理は invokeConstructor() メソッドをオーバーライド して実装する。 生成したいインスタンスを返り値にする
とします。 前回との違いは、オーバライドするメソッドが invokeMethod() ではなく invokeConstructor() であるところです。 このクラスをコンパイルしてクラスパス上に配置すると、以下のようなコードが実行できます:
import org.sample.Person def j = new Person('Joyce') println j // org.sample.Person(j, 200)
Groovy のコンストラクタ関連の技法で思いついたのはこんなもんでしたが、他によく使いそうなものあったかな? AST 変換を自作したりするのも出来るといいかもしれないけど、今回はパス。
- 作者: 関谷和愛,上原潤二,須江信洋,中野靖治
- 出版社/メーカー: 技術評論社
- 発売日: 2011/07/06
- メディア: 単行本(ソフトカバー)
- 購入: 6人 クリック: 392回
- この商品を含むブログ (152件) を見る
*1:フィールドを null や 0 で初期化したりはする。