Apache Groovy "as" オペレータの正体
これはG* Advent Calendar 17日目の記事です。
ある値を型Aから型Bに変換するといったコードは頻繁に出てきますよね。
キャストで行えるものが最もシンプルですが、キャストだと変換できないパターンというものが大量に存在します。
例えば以下のコードはそれぞれ例外が発生します。
Integer a = "123" Integer a = (Integer) "123"
例外の内容は共に、キャストできません、というものです。
org.codehaus.groovy.runtime.typehandling.GroovyCastException: Cannot cast object '123' with class 'java.lang.String' to class 'java.lang.Integer'
さて、そこで今回のネタである強制変換オペレータas
を使ってみます。
Integer a = "123" as Integer
なんと、こちらは問題なく動作しました。
ということは、どうやらキャストとasオペレータは別物のようです。
asの正体
このasオペレータ、実はレシーバに定義されているasType
というメソッドを実行しています。
このasType()はクラス自体を引数として取り、その指定されたクラスを元にどんな変換処理をするか、という処理を定義することが出来ます。
なので、上記のコードは
Integer a = "123".asType(Integer)
と等価です。
つまり上記のコードは、"123"
というStringクラスのインスタンスなので、Stringクラスに定義されているasType
メソッドが実行されて、その中でIntegerの値を生成してreturnしてくれている、ということになります。
具体例
class Person { String name Integer age String toString() { "Person name:${name}, age:${age}" } // これ! def asType(Class convertTo) { // 別にクラスは一つだけじゃなくて、複数指定することも出来る。 switch (convertTo) { case Developer: return new Developer(person: this, language: "Apache Groovy") break; case Integer: return age default: throw new ClassCastException("Person cannot be coerced into ${convertTo}") } } } class Developer { Person person String language String toString() { "${language} Developer!! ---> ${person}" } } Person p = new Person(name: "koji", age: 32) // 継承関係も何も無いクラスへ変換! Developer d = p as Developer assert p instanceof Person assert !(p instanceof Developer) assert d instanceof Developer assert !(d instanceof Person) assert p.toString() == "Person name:koji, age:32" assert d.toString() == "Apache Groovy Developer!! ---> Person name:koji, age:32" assert 32 == p as Integer // 参照を持っているのでメンバ変数を変更すると両方変る。 p.age = 99 assert p.toString() == "Person name:koji, age:99" assert d.toString() == "Apache Groovy Developer!! ---> Person name:koji, age:99" // Integerにもasで変換できるようにしている。 assert 99 == p as Integer
Groovyにもtraitが追加された今では、asTypeで実現できることはtraitでも出来るような気がするので、自分のコードでasTypeを定義する機会はあまり無いな気もしますが、普段何気なく使っているasオペレータの動作を知ることで一歩Groovyの深みに近づけた感じです。