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 があります。