Groovy 2.5.0で追加されたgroovy.transform.AutoFinalを試してみた

最新のGroovy 2.5.0から、groovy.transform.AutoFinalというAST変換が追加されていたらしい。

ついさっきPaul King氏のツイートを目にして初めて知った。

気になったので早速試してみた。

まずは@AutoFinalなし。

class Person {
    String firstName
    String lastName

    Person(String firstName, String lastName) {
        lastName = lastName.toUpperCase() // あえてパラメータに再代入する(普通はこんなことしちゃダメ)
        this.firstName = firstName
        this.lastName = lastName
    }

    String toString() {
        "$firstName $lastName"
    }
}

def p = new Person("Nobunaga", "Oda")
assert p.toString() == "Nobunaga ODA"

普通に実行できる。

で、クラスに@AutoFinalアノテーションをつけてみる。

import groovy.transform.*

@AutoFinal // <--- これを追加しただけ
class Person {
    String firstName
    final String lastName

    Person(String firstName, String lastName) {
        lastName = lastName.toUpperCase() // あえてパラメータに再代入する(普通はこんなことしちゃダメ)
        this.firstName = firstName
        this.lastName = lastName
    }

    String toString() {
        "$firstName $lastName"
    }
}

def p = new Person("Nobunaga", "Oda")
assert p.toString() == "Nobunaga ODA"

これを実行しようとすると、

1 compilation error:
The parameter [lastName] is declared final but is reassigned

と、コンパイルエラーになった。

というように、@AutoFinalをつけると、AST変換によって全てのコンストラクタ引数、メソッド引数、クロージャ引数に暗黙のfinalが付与されて、コンパイルチェックされるようになる。

これはいいかもしれない。

(おまけ) プロパティのfinal化=不変化

なお、

import groovy.transform.*

@AutoFinal
class Person {
    String firstName
    String lastName

    Person(String firstName, String lastName) {
        //lastName = lastName.toUpperCase() // このエラーは今は興味ないのでコメントアウト
        this.firstName = firstName
        this.lastName = lastName
    }

    String toString() {
        "$firstName $lastName"
    }
}

def p = new Person("Nobunaga", "Oda")
assert p.toString() == "Nobunaga Oda"

// プロパティは再代入可能
p.firstName = "Nobunari"
assert p.toString() == "Nobunari Oda"

は普通に実行できる。つまりプロパティは再代入可能なままなので注意。

プロパティを再代入不可能にするには、groovy.transform.Immutableという前からあるAST変換を使えばOK。 簡単に不変オブジェクトにできる。

import groovy.transform.*

@Immutable
class Person {
    String firstName
    String lastName

    // @Immutableを使った場合は、Mapを受け取るコンストラクタと
    // プロパティ定義順にしたがって全部受け取るコンストラクタが自動生成される。
    // 明示的なコンストラクタを実装するとエラーになる。
    // Error during ImmutableBase processing. Explicit constructors not allowed for class: Person
    //
    //Person(String firstName, String lastName) {
    //    //lastName = lastName.toUpperCase() // このエラーは今は興味ないのでコメントアウト
    //    this.firstName = firstName
    //    this.lastName = lastName
    //}

    String toString() {
        "$firstName $lastName"
    }
}

def p = new Person("Nobunaga", "Oda")
assert p.toString() == "Nobunaga Oda"

// プロパティは再代入不可能になる
p.firstName = "Nobunari"
assert p.toString() == "Nobunari Oda"
groovy.lang.ReadOnlyPropertyException: Cannot set readonly property: firstName for class: Person

コンパイルエラーではなく、実行時に例外が発生する点がちょっと違う。