GroovyでJavaのpackage-info.javaを一括生成するスクリプト(二番煎じ)
元ネタ
実行結果
$ groovy generatePackageInfo.groovy 出力完了。出力結果 ./src/jp/co/a/a1/package-info.java ./src/jp/co/a/a2/package-info.java ./src/jp/co/a/a3/package-info.java ./src/jp/co/a/package-info.java ./src/jp/co/b/b1/package-info.java ./src/jp/co/b/b2/package-info.java ./src/jp/co/b/package-info.java ./src/jp/co/package-info.java ./src/jp/package-info.java $ cat ./src/jp/co/a/a1/package-info.java /** * jp.co.a.a1パッケージ。 * * <pre> * // TODO パッケージ内容の詳細を記述してください * </pre> * */ package jp.co.a.a1; $
おわりに
- 勢いでお題化してみました。他の言語でも是非解いてみて下さい。
Groovyによるうるう年チェック
当メモは [お題] うるう年チェック | Think Twice へ移管しました。
お題:階層のナンバリング
プログラミングお題の一覧はこちら
※みなさんもこのお題をお気に入りの言語で解いてみて下さい。解いたらこの記事にトラックバックをお願いします。
説明
以下例のように、カンマと1で階層を表現するデータ入力がある。 階層ごとに出現順にインクリメントしナンバリングした結果を出力したい。 以下の条件に従い、結果を出力するプログラムを書け。 ※条件適切にエラー処理を行うこと <条件> ・入力の一行の中で1は一回のみ出現すること(二回以上の出現はエラー) ・ある行の階層は、直前の行の階層+1を超えないこと(超えた場合エラー) ・ある階層より上位の階層がインクリメントされた場合、それより下層は1にリセットすること ・空行があった場合、空行のまま出力すること <入力例> 1 ,1 ,,1 ,,1 ,1 ,,1 1 1 ,1 <出力例> 1 ,1 ,,1 ,,2 ,2 ,,1 2 3 ,1 <エラー入力1> 1 ,1 ,1,1 <エラー入力2> 1 ,1 ,,,1
Groovyで既存クラスのasTypeをOverrideする
はじめに
Groovyでは as演算子 を利用するためには #asType(Class) メソッドを実装すればよい。既存クラスには既に #asTypeメソッド が実装されているため、通常あまりいじることはない(と思われる)のだが、とある事情でいじる必要が出てきた。その際、ちとハマッたのでメモ。
文字列を自作のクラスに as する
ちと例が悪いのですが、こないだのポーカーのやつで利用したCardクラスを考える。
enum Suit { S('♤♠'), H('♡♥'), D('♢♦'), C('♧♣'), def mark Suit(mark){ this.mark = mark } } enum Rank { R2, R3, R4, R5, R6, R7, R8, R9, R10, RJ, RQ, RK, RA def rank(){ this.name().substring(1) } } class Card { Suit s Rank r String toString() { switch(s) { case [Suit.H, Suit.D] : return "${s.mark[0]}${r.rank()}" case [Suit.S, Suit.C] : return "${s.mark[1]}${r.rank()}" } } }
enumのスート(Suit)はトランプの柄を表しており、同じくenumのRankはトランプのランク(A〜K)を表している。*1
そして、SuitとRankをコンポジションとして持つクラス、カード(Card)を定義した。
ここで、以下のような文字列から Card のインスタンスに型変換することを考える。
- 'SA' -> スペードのエース
- 'C5' -> クローバーの5
こんな感じにしたい
Card c1 = 'SA' as Card // -> スペードのエース Card c2 = 'C5' as Card // -> クローバーの5
そこで、文字列クラス(String)の as を定義する。冒頭でも書いたが、Groovyではas演算子の定義にはasTypeを定義すればよい。ただしStringのasは既に定義されているため、既存の実装は残しつつ新たなルールを追加する形で実装する必要がある。
StringのasTypeを再定義
String.metaClass.define { oldAsType = String.metaClass.getMetaMethod("asType", [Class] as Class[]) asType = { Class c -> if (c == Card) new Card(s:delegate[0] as Suit, r:'R'+delegate[1..-1] as Rank) else oldAsType.invoke(delegate, c) } }
oldAsTypeに定義前のasTypeメソッドの保持しておき、ClassがCardの場合とそれ意外の場合で処理を切り分けるようにしている。
- Cardの場合は文字列はenumにasできることを利用して、SuitとRankそれぞれに分解して型変換している
- Cardでなかった場合は保持しておいた定義前のasTypeをinvokeして変換した結果を返却
Cardだった場合の定義は再帰的になっているが、うまく動作する。
宿題
ちなみに、oldAsTypeで定義前のメソッドを保持しておく際に、以下のようにMethodClosureの形だとうまく動かなかった。
String.metaClass.define { oldAsType = String.metaClass.&asType // <- MethodClosure asType = { Class c -> if (c == Card) new Card(s:delegate[0] as Suit, r:'R'+delegate[1..-1] as Rank) else oldAsType(c) // <- MethodClosure } }
Caught: groovy.lang.MissingMethodException: No signature of method: overrideAsType_err.oldAsType() is applicable for argument types: (java.lang.Class) values: [class Suit] Possible solutions: asType(java.lang.Class) groovy.lang.MissingMethodException: No signature of method: overrideAsType_err.oldAsType() is applicable for argument types: (java.lang.Class) values: [class Suit] Possible solutions: asType(java.lang.Class) at overrideAsType_err$_run_closure1_closure2.doCall(overrideAsType_err.groovy:29) at overrideAsType_err$_run_closure1_closure2.doCall(overrideAsType_err.groovy:27) at overrideAsType_err.run(overrideAsType_err.groovy:33)
oldAsType()は java.lang.Class を受け取るんだよ! [class Suit] じゃダメなんだからね!ってことらしい…、SuitもClassのはずなんだが…。
とりあえずgetMetaMethodを使っておけばうまくいくので、いずれこの問題も調査しよう。
おわりに
実はこの完成までに定義が再帰してしまったりして、色々調べてここまで辿り着きました。EMCは奥が深い。
参考
Javaで型トークンを利用したファクトリクラスを書いてみた
はじめに
Javaでインスタンスを生成するクラス、いわゆるファクトリクラスを書く場合、色々な書き方があると思いますが、特定のクラスのインスタンスのみしか作れないとわりと不便なので、今回は、型トークン(クラスクラスと呼ばれるやつですね: Class<T> です)を引数として渡してそのクラスのインスタンスを返却してくれるとファクトリを書いてみました。
さらにそのファクトリを通して一度生成しているインスタンスについては同じものを返してくれるとメモリの節約にもなってよいですね。
というわけで、早速ソースとテストコードです
説明
とりあえず、クラスがないと説明しにくいのでテスト用に以下のような階層のクラスを作ってあります。
Alphabet <-- 抽象クラス |- A <-- 具象クラスその1 |- B <-- 具象クラスその2 +- C <-- 具象クラスその3
さて、ここで、Aのインスタンスを取得するためのファクトリの使い方は以下のようになります。
// Alphabetのサブクラス用のファクトリ Factory<Alphabet> factory = new Factory<Alphabet>(); // Aのインスタンスを取得 A a = factory.getOrCreate(A.class);
#getOrCreateメソッドに型トークン(Class<A>)を渡すことにより、Aのインスタンスを取得できています。ファクトリ内部では、型トークンをキーとしたマップにインスタンスが保持されているため、再度 #getOrCreate(A.class) を呼び出した場合は同じインスタンスが取得されます。
A a1 = factory.getOrCreate(A.class); A a2 = factory.getOrCreate(A.class); a1 == a2; // => true: 同じインスタンス
もちろん、型トークンを変えれば違う型のインスタンスも取得できます。
B b = factory.getOrCreate(B.class); C c = factory.getOrCreate(C.class);
ただし、Factory<T>はジェネリックなクラスとし、Tという型パラメータで指定されたクラスのサブクラスのみ生成可能となるよう制限をかけています。
#getOrCreateメソッドもジェネリックになっており、型パラメータUは<U extends T>としてありますので、Tのサブクラスの型トークンのみを引数として取れる、という風になっています。
public <U extends T> U getOrCreate(Class<U> clazz) { ... }
蛇足
昔、Java1.4の頃、これと同様のことをやりたくて、ファクトリクラスには生成する必要があるクラス分だけ、getterメソッドを作った記憶があります…今となってはいい思い出です。
class Factory14 { Foo createFoo() { ... } Bar createBar() { ... } Baz createBaz() { ... } . . . }
参考書籍
Effective Java 第2版 (The Java Series)
- 作者: Joshua Bloch,柴田芳樹
- 出版社/メーカー: ピアソンエデュケーション
- 発売日: 2008/11/27
- メディア: 単行本(ソフトカバー)
- 購入: 57人 クリック: 745回
- この商品を含むブログ (247件) を見る
主に第5章:ジェネリックスと項目29を参照