Groovy のトレイトと @Immutable

Groovy 2.3 からトレイト機能が追加されています。

ここで、トレイトと @Immutable アノテーションを共に使用した場合、現バージョン (2.3) では以下のような注意点がありました。

  • トレイトで定義したプロパティの値は変更可能 (immutable とはならない)
  • マップベースコンストラクタで値を設定するには "<トレイト名>__<プロパティ名>" と指定する必要あり

検証に使用したサンプルスクリプトは下記です。

ソースは http://github.com/fits/try_samples/tree/master/blog/20140504/

sample.groovy
import groovy.transform.*

interface Pricing {
    BigDecimal total()
}
// トレイトの定義1
trait BasicPricing implements Pricing {
    BigDecimal price = 0
    BigDecimal total() { price }
}
// トレイトの定義2
trait QuantityPricing extends BasicPricing {
    int qty
    BigDecimal total() { price * qty }
}
// 実装クラス1
@ToString
class Sample1 implements QuantityPricing {
    String name
}
// 実装クラス2
@Immutable
class Sample2 implements QuantityPricing {
    String name
}

println '----- 1 -----'
// (a) トレイトのプロパティ名を指定して値を設定できる
def s1 = new Sample1(name: 'S1', price: 100, qty: 5)
println "${s1}, total: ${s1.total()}, price: ${s1.price}"
println s1.dump()

println ''
println '----- 2a -----'
// (b) @Immutable の場合はトレイトのプロパティ名を指定しても値を設定できない (初期値のまま)
def s2a = new Sample2(name: 'S2a', price: 200, qty: 6)
println "${s2a}, total: ${s2a.total()}, price: ${s2a.price}"
println s2a.dump()
// (c) トレイトで定義したプロパティには @Immutable なクラスでも値を書き込める
s2a.price = 300
s2a.qty = 3
println "${s2a}, total: ${s2a.total()}, price: ${s2a.price}"
println s2a.dump()

println ''
println '----- 2b -----'
// (d) @Immutable なクラスの場合は "<トレイト名>__<プロパティ名>" で値を設定する必要あり
def s2b = new Sample2(name: 'S2b', BasicPricing__price: 200, QuantityPricing__qty: 6)
println "${s2b}, total: ${s2b.total()}, price: ${s2b.price}"
println s2b.dump()

上記 (c) のように、トレイトで定義したプロパティ (price や qty) は実装クラスへ @Immutable アノテーションを付与していても値を変更する事が可能です。

マップベースコンストラクタを使用する場合、通常は (a) のようにトレイトのプロパティ名 (price や qty) を指定して値を設定できますが、@Immutable なクラスの場合は (d) のように <トレイト名>__<プロパティ名> とする必要があるようです。

(b) のようにコンストラクタで通常のプロパティ名を指定しても値を設定できませんでした。

実行結果は以下の通りです。

実行結果
> groovy sample.groovy

----- 1 -----
Sample1(S1), total: 500, price: 100
<Sample1@3d285d7e name=S1 QuantityPricing__qty=5 BasicPricing__price=100>

----- 2a -----
Sample2(S2a), total: 0, price: 0
<Sample2@14d63 QuantityPricing__qty=0 BasicPricing__price=0 name=S2a $hash$code=85347>
Sample2(S2a), total: 900, price: 300
<Sample2@14d63 QuantityPricing__qty=3 BasicPricing__price=300 name=S2a $hash$code=85347>

----- 2b -----
Sample2(S2b), total: 1200, price: 200
<Sample2@14d64 QuantityPricing__qty=6 BasicPricing__price=200 name=S2b $hash$code=85348>

dump() の結果より、トレイトで定義したプロパティは実装クラス内で <トレイト名>__<プロパティ名> フィールドとして定義されている事が分かります。