日記 (2016 年 6 月 11 日)
Groovy は JVM 上で動くスクリプト言語です。 文法がほとんど Java と同じなので覚えるのも簡単で、 Java のライブラリがそのまま使えて便利です。 さらに、 スクリプト言語というと実行速度が怪しまれますが、 静的コンパイルを行うことで (動的型付け言語としての機能が制限されますが) Java 並の速度が出ます。 ということで、 Groovy の宣伝でした。
さて、 Groovy ではプリミティブ型は必ずそのラッパークラスとして扱われます (厳密には微妙に違うみたいですけど)。
つまり、 int
型で変数を宣言しても内部では Integer
型として扱われるので、 普通に toString
メソッドなどが呼べます。
それなら、 int
なんて書いて変数を宣言せずに Integer
と書いた方が統一感が出るし良いと思いませんか?
結局は同じなのでどちらで書いても良いんですが、 1 つだけ注意点があります。
例えば、 Java で書かれたクラスに int
を引数にとるメソッド foo
が定義されていたとして、 Groovy でこのクラスのサブクラスを作り、 メソッド foo
をオーバーライドすることを考えます。
このとき、 当たり前といえば当たり前ですが、 引数の型を Integer
にするとオーバーライドだと見なされません。
でも、 こういうときだけ int
にするのもなんか統一感がなくて気持ち悪いですよね?
こんなときに登場するのが、 AST 変換です。
これは何かというと、 バイトコードにコンパイルするときに、 ソースコードから生成された構文木に手を加えられる機能です。
これを用いて、 Integer
型のメソッド引数をコンパイル時に int
型にしてしまいましょう。
まずは、 どのメソッドの引数の型を変換するかを指定するアノテーションを作ります。 いたって普通です。
@Retention(RetentionPolicy.SOURCE)
@Target([ElementType.METHOD])
@GroovyASTTransformationClass(["ziphilib.transform.ConvertPrimitiveTransformation"])
@CompileStatic
public @interface ConvertPrimitive {
}
GroovyASTTransformationClass
というアノテーションが重要で、 構文木に手を加えるときのその内容が書かれたクラスをこれで指定します。
さて、 引数の型を置き換えるためのクラスが以下です。
visit
メソッドが実際に構文木を書き換えている部分です。
@GroovyASTTransformation(phase=CompilePhase.SEMANTIC_ANALYSIS)
@CompileStatic
public class ConvertPrimitiveTransformation implements ASTTransformation {
public void visit(ASTNode[] nodes, SourceUnit sourceUnit) {
MethodNode method = (MethodNode)nodes[1]
method.getParameters().each() { Parameter parameter ->
String typeName = parameter.getType().getName()
if (typeName == "java.lang.Integer") {
parameter.setType(ClassHelper.make("int"))
}
}
}
}
まず、 GroovyASTTransformation
というアノテーションですが、 これは構文木をバイトコードにするどの段階で visit
メソッドを実行するかを指定しています。
詳しくは別のところを見てください。
で、 実際の処理内容が書かれる visit
メソッドですが、 引数として nodes
と sourceUnit
が渡されます。
nodes
は要素数が 2 の配列で、 使われたアノテーションそのものの構文木データと、 そのアノテーションがつけられたメソッドなどの構文木データが、 順に格納されています。
今回はメソッドの内容 (引数の型) を書き換えるので、 2 番目の要素を使います。
ここで得られたメソッドの構文木データ method
から、 getParameters
メソッドで引数データを取得します。
each
メソッドを用いて、 各引数データ parameter
それぞれに対して処理を行います。
さて、 parameter
から getType
メソッドで引数の型データを取得し、 getName
メソッドでその名前を取得します。
ここで得られる型の名前は、 パッケージ名を含む完全修飾名です。
これが Integer
だったときに、 int
に変換したいわけです。
そこで、 int
型を表す型データが必要なわけですが、 それは ClassHelper
クラスの静的メソッド make
を使うと簡単に作れます。
とそんなわけで、 やりたいことをするコードが書けたので、 これをコンパイルして、 型を変換したいメソッドに ConvertPrimitive
アノテーションをつけておけば、 めでたく変換されます。