metaClass

以前 uehaj 氏が指摘されたエントリ

ExpandoMetaClass.enableGlobally() で metaClass が変化するのは同じだけど振る舞いは変わっているようなのでメモしておく。

  • この辺りは明文化されていないのでこれからも変わるかもしれない
  • assert のコメントが付いているものは 1.7 と 1.8 で振る舞いが違ったもの
  • 本当は TCO をやりたかっただけなのでメソッドの追加ではなく更新系
  • 思いつくままに調べた
  • デフォルト引数と書いているがそれが影響しているのかはわからない
  • ソースは追ってない
// ExpandoMetaClass.enableGlobally() 影響なし
class HasMethod {
  def num() { 1 }
  static def main(args) {
    println "modify method"

    // 初期状態
    def obj = new HasMethod()
    def ref = obj.&num
    assert obj.metaClass in MutableMetaClass
    assert !obj.metaClass.modified
    assert !obj.class.metaClass.modified
    assert 1 == obj.num()
    assert 1 == ref()

    // インスタンスの metaClass を更新する
    obj.metaClass.num = { 10 }
    assert obj.metaClass.modified
    assert !obj.class.metaClass.modified
    assert 10 == obj.num()
    assert 1  == ref()
    assert 1  == new HasMethod().num()

    // クラスの metaClass を更新する
    obj.class.metaClass.num = { 100 }
    assert obj.metaClass.modified
    assert obj.class.metaClass.modified
    assert 10  == obj.num()
    assert 100 == ref()
    assert 100 == new HasMethod().num()
  }
}
HasMethod.main()


class HasStaticMethod {
  static def num() { 1 }
  static def main(args) {
    println "modify static method"

    // 初期状態
    def ref = HasStaticMethod.&num
    assert !HasStaticMethod.metaClass.modified
    assert 1 == HasStaticMethod.num()
    assert 1 == ref()

    // クラスの metaClass を更新する
    HasStaticMethod.metaClass.'static'.num = { 10 }
    assert HasStaticMethod.class.metaClass.modified
    assert 10 == HasStaticMethod.num()
    assert 10 == ref()
  }
}
HasStaticMethod.main()


class HasCallingMethod {
  def say() {
    greeting()
  }
  def greeting() {
    "Hello, World!"
  }
  static def main(args) {
    println "modify called method"

    // 初期状態
    def obj = new HasCallingMethod()
    def ref = obj.&greeting
    assert !obj.metaClass.modified
    assert "Hello, World!" == obj.say()
    assert "Hello, World!" == obj.greeting()
    assert "Hello, World!" == ref()

    // インスタンスの metaClass を更新する
    obj.metaClass.greeting = { "Hello, Groovy!" }
    assert obj.metaClass.modified
    assert !obj.class.metaClass.modified
    assert "Hello, World!"  == obj.say()
//    assert "Hello, Groovy!" == obj.say()  // 1.7
    assert "Hello, Groovy!" == obj.greeting()
    assert "Hello, World!"  == ref()

    // クラスの metaClass を更新する
    obj.class.metaClass.greeting = { "Hello, Groovy!" }
    assert obj.metaClass.modified
    assert obj.class.metaClass.modified
    assert "Hello, Groovy!" == obj.say()
    assert "Hello, Groovy!" == obj.greeting()
    assert "Hello, Groovy!" == ref()
  }
}
HasCallingMethod.main()


class HasRecursion {
  def fact(n) {
    // println "fact($n)"
    n == 0 ? 1 : n * fact(n-1)
  }
  static def main(args) {
    println "modify recursive method"

    // 初期状態
    def obj = new HasRecursion()
    def ref = obj.&fact
    assert !obj.metaClass.modified
    assert 3628800 == obj.fact(10)
    assert 3628800 == ref(10)

    // インスタンスの metaClass を更新する
    obj.metaClass.fact = { n -> n }
    assert obj.metaClass.modified
    assert !obj.class.metaClass.modified
    assert 0  == obj.fact(0)
    assert 1  == ref(0)
    assert 90 == ref(10)

    // 元に戻してみる
    obj.metaClass.fact = ref
    assert obj.metaClass.modified
    assert !obj.class.metaClass.modified
    assert 90  == obj.fact(10)
  }
}
HasRecursion.main()


class HasDefaultArgs {
  def sum(n, acc=0) {
    // println "sum($n,$acc)"
    if (n == 0)
      acc
    else
      sum(n-1, acc+n)
  }
  static def main(args) {
    println "modify default args method"

    // 初期状態
    def obj = new HasDefaultArgs()
    def ref = obj.&sum
    assert [Object, Object] == ref.parameterTypes
    assert !obj.metaClass.modified
    assert 55 == obj.sum(10)
    assert 55 == ref(10)

    // インスタンスの metaClass を更新する
    obj.metaClass.sum = { n, acc=1 -> acc }
    assert [Object, Object] == ref.parameterTypes
    assert obj.metaClass.modified
    assert !obj.class.metaClass.modified
    assert 1  == obj.sum(10)
    assert 10 == ref(10,0)
    assert 0  == ref(10)
  }
}
HasDefaultArgs.main()


class HasDefaultArgsType {
  def sum(Integer n, Integer acc=0) {
    // println "sum($n,$acc)"
    if (n == 0)
      acc
    else
      sum(n-1, acc+n)
  }
  static def main(args) {
    println "modify default args method 2"

    // 初期状態
    def obj = new HasDefaultArgsType()
    def ref = obj.&sum
    assert [Integer, Integer] == ref.parameterTypes
    assert !obj.metaClass.modified
    assert 55 == obj.sum(10)
    assert 55 == ref(10)

    // インスタンスの metaClass を更新する
    obj.metaClass.sum = { Integer n, Integer acc=1 -> acc }
    assert [Integer, Integer] == ref.parameterTypes
    assert obj.metaClass.modified
    assert !obj.class.metaClass.modified
    assert 1  == obj.sum(10)
    assert 10 == ref(10,0)
    assert 10 == ref(10)
//    assert 0  == ref(10)  // 1.7
  }
}
HasDefaultArgsType.main()

2011-06-04 追記

クラスの拡張には metaClass を直接使わなくても use, Category, mixin があります。
特異オブジェクトが必要なら Expando があります。