Groovy の MetaClass でメソッドをオーバーライドする時にはまったこと

Groovy の便利な仕様として、メタプログラミングがあります。

クラスやメソッドを呼び出すときに、そのクラスの定義を変更して振る舞いを変えることができます。
その実装方法はシンプルでわかりやすいものとなっています。

ですが、はまりました

シンプルなコードで MetaClass を試してみたところ簡単に実装できました。

これは使えるとばかりに仕事で使ってみたところ、うまくメソッドがオーバーライドされなくて悩みました。試行錯誤してオーバーライドされない原因がわかりました。

型指定したパラメータをもつメソッドをオーバーライドするときは、Closure のパラメータに型指定しないといけません。

以下、サンプルコードです。
まずはオーバーライドする対象のクラスです。

class Sample {

    String execute(String val) {
        val
    }
}

次に、オーバーライド失敗しているテストケースと成功しているテストケースです。

class MetaProgramingTest extends spock.lang.Specification {

    def "引数ありメソッドをメタプログラミングでオーバーライドする - 失敗パターン"() {
        given:
        JavaSample sample = new JavaSample()
        sample.metaClass.execute = { val -> "2" } 
        
        when:
        def val = sample.executeParam("1")
        
        then:
        val == "1"
    }
    
   def "引数ありメソッドをメタプログラミングでオーバーライドする - 成功パターン"() {
        given:
        JavaSample sample = new JavaSample()
        sample.metaClass.execute = { String val -> "2" }
        
        when:
        def val = sample.executeParam("1")
        
        then:
        val == "2"
    }
}

よくよく考えてみたら、型指定しないとオーバーロードしているメソッドがあった場合、どのメソッドをオーバーライドするか判断できないですよね。
それが本当の理由かわかりませんが、たぶん理由のひとつでしょう。

Groovy 初級的な文脈でよく MetaClass が紹介されているのですが、この「引数ありメソッドのオーバーライド」ってあんまり詳しく言及されていないと思います。

Groovy を使っていると状況に応じて型指定したり、しなかったりします。メタプログラミングのときは気をつけないといけませんね。

ちなみに

次のようにオーバーライド対象のメソッドの引数の型指定しない場合、上記どちらのテストケースでもオーバーライドできます。

class Sample {

    String execute(val) {
        val
    }
}

動的型付けに対する理解が少し深まった気がします。