Groovy の型変換
型変換についてのメモ。
プログラムの挙動がおかしいと思ったときに読み返すためのもの。
Scala の implicit conversions
implicit conversions が試されるのは
- 要求された型への変換
- 変数宣言の型への変換
- メソッド呼び出しの引数の型への変換
- レシーバの変換
Groovy では use キーワードによるカテゴリがこの機能に対応する。
2 は Groovy では引数の型でメソッドを選択するので対応するメソッドを用意することになる。
1 はカテゴリでは対応できない。
Groovy の型変換
強制型変換
演算によって型が変わる
- 演算で Double か Float を使用すると Double になる
- 演算で BigDecimal を使用すると BigDecimal になる
- 演算で BigInteger を使用すると BigInteger になる
- 演算で Long を使用すると Long になる
- 上記以外の場合は Integer *1
Groovy の暗黙の型変換
Java のように型を意識して使用している場合には関係のない話。
Groovy には short や byte、char などのリテラルがないのでそれらを宣言する場合は右辺は int や String になる。これらは Groovy が自動的に変換してくれる。
char c = 'A' // as char を省略。シングルクォートで囲んであるが Groovy では String short s = 1 // as short を省略。narrowing だが明示しなくても可能 int i = 'A' // 65
Groovy ではこのような場合 ClassCastException が発生しないので注意。
- http://jira.codehaus.org/browse/GROOVY-2608?page=com.atlassian.jira.plugin.system.issuetabpanels%3Aall-tabpanel
- http://groovy.codehaus.org/JN3015-Types
おそらく Groovy 側で () によるキャストを埋め込んでくれている。
これが as に変われば Scala の implicit conversions の 1 と同じ機能を実現できる。
DefaultTypeTransformation に TODO が書かれていた。
public static Object castToType(Object object, Class type) { if (object == null) { return null; } if (type == Object.class) return object; final Class aClass = object.getClass(); if (type == aClass) return object; // TODO we should move these methods to groovy method, like g$asType() so that // we can use operator overloading to customize on a per-type basis if (ReflectionCache.isArray(type)) {
いつ対応されるか分からないので気長に待つとする。
そう言えば半年前は nested class で enum を使えなかったが今は利用できる *3 ので案外早いかもしれない。
さらなる疑問
Groovy でリストのかけ算 を確認しているときにタイプミスして気づいたのだがずっと理由が分からなかった。
assert 1 * [2] == 2
- 宣言上は multiply の引数は List をとらない
- List は宣言されている引数の型にはキャストできない
- 要素数が 0 や 2 以上ではエラーになる
intdiv の場合
assert 1.intdiv([1,2,3,4,5,6,7,8,9]) == 1
これ以降の処理を調べてきれていないが実行時にうまく処理されているようだ。
import org.codehaus.groovy.runtime.callsite.* def acallsite = new CallSiteArray(this.class, ["runScript", "println", "multiply"] as String[]).array assert acallsite[2].call(1, [[2]] as Object[]) == 2 // acallsite[1].callCurrent(this, acallsite[2].call(1, [2]));
アノテーションや型パラメータ
Groovy 1.8 から Closure に戻り値の型パラメータが追加されていた。
でも Groovy でコーディングするときは @Override や型パラメータはコメント代わりにしか使っていない*4。
@Override def test() { List<Long, Double> list = new ArrayList<Integer>() list.add("s") }
test を Override しているわけではないし List の型パラメータは意味不明だ。
これでも通ってしまう。
型チェックが欲しい場合 ???
def seq = new Sequence(Integer) // extends ArrayList seq.add(1) seq.add(2G) // IllegalArgumentException
追記 2011-05-14
強制型変換の例外のことを書くのを忘れていたので追記
groovy:000> 5.0d ** 2 ===> 25
25.0d ではない。
べき乗の計算は整数になると整数型で返している。
public static Number power(Number self, Number exponent) { double base, exp, answer; base = self.doubleValue(); exp = exponent.doubleValue(); answer = Math.pow(base, exp); if ((double) ((int) answer) == answer) { return (int) answer; } else if ((double) ((long) answer) == answer) { return (long) answer; } else { return answer; } }
強制型変換は +, -, * だけで他の演算は例外あるので注意する。
あと今は戻り値の型へのキャストが行われていないということもわかる。
この問題も計算結果を一度 double に代入しておくかキャストを明示しておけばエラーにならない*5。