saba1024のブログ

どうでも良い思いついた事とかプログラミング関係のメモとか書いていきます。

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の深みに近づけた感じです。