20

The following code tried to replace an existing method in a Groovy class:

class A {
  void abc()  {
     println "original"
  }
} 

x= new A()
x.abc()
A.metaClass.abc={-> println "new" }
x.abc()
A.metaClass.methods.findAll{it.name=="abc"}.each { println "Method $it"}

new A().abc()

And it results in the following output:

original
original
Method org.codehaus.groovy.runtime.metaclass.ClosureMetaMethod@103074e[name: abc params: [] returns: class java.lang.Object owner: class A]
Method public void A.abc()
new

Does this mean that when modify the metaclass by setting it to closure, it doesn't really replace it but just adds another method it can call, thus resulting in metaclass having two methods? Is it possible to truly replace the method so the second line of output prints "new"?

When trying to figure it out, I found that DelegatingMetaClass might help - is that the most Groovy way to do this?

Jean Barmash
  • 4,788
  • 1
  • 32
  • 40

4 Answers4

15

You can use the per-instance metaClass to change the value in the existing object like so:

x= new A()
x.abc()
x.metaClass.abc={-> println "new" }
x.abc()
x.metaClass.methods.findAll{it.name=="abc"}.each { println "Method $it"}

But as you have seen, x will have two methods associated with it (well, in actual fact, a method and the closure you added

If you change the definition of A so that the method becomes a closure definition like so:

class A {
  def abc = { ->
     println "original"  
  }
} 

Then you will only get a single closure in the metaClass and no method after the alteration

tim_yates
  • 167,322
  • 27
  • 342
  • 338
  • 3
    Thanks - I am trying to use this to override a method in existing Groovy class, so I am trying to avoid modifying the original class. – Jean Barmash Mar 15 '10 at 14:46
5

I fully agree with @tim_yates on this. But, there is a way around, if you want to avoid modifying the original class use MethodClosure instead, as shown below:

class A {
  void abc()  {
     println "original"
  }
} 

x = new A()

//Create a Method Closure or Method pointer
pointer = x.&abc

//Replace Original call with Method pointer
//x.abc()
pointer()

//Meta classed
A.metaClass.abc={-> println "new" }

//Call method pointer instead of original again
//x.abc()
pointer()

A.metaClass.methods.findAll{it.name=="abc"}.each { println "Method $it"}

new A().abc()

You should get what is expected:

original
new
Method org.codehaus.groovy.runtime.metaclass.ClosureMetaMethod@43094309
  [name: abc params: [] returns: class java.lang.Object owner: class A]
Method public void A.abc()
new

Based on Groovy 2.2.1. The question is too old though.

dmahapatro
  • 49,365
  • 7
  • 88
  • 117
  • Struggling to understand `MethodClosure` here. Using this approach is it possible to have two instances of A (say x and y) each have their own separate methods `abc`? – Dave Jan 02 '14 at 16:03
  • No. Once you have modified `abc` using the metaClass on `Class A` then it is applicable to all instances of A. MethodClosure is only a Closure representation of the underlying method which can be invoked at any point of time. – dmahapatro Jan 02 '14 at 17:43
  • ok - so it is not possible to have several instances of `Class A` each with a different method `abc`. Bummer. – Dave Jan 05 '14 at 22:44
  • You can, but every time you have to modify the metaClass and do `A.metaClass.abc={-> println "brand new" }` if you want to see "brand new" for every new instance. Does it make sense? – dmahapatro Jan 05 '14 at 23:49
  • Ok - so the Instances made prior to changing the metaClass use the method at them time they were created. Instances made after changing the metaClass use the new method. If I wanted to, I could change the metaClass right before every instance creation. Do I understand? – Dave Jan 06 '14 at 20:44
  • Yes exactly, that would suffice if you use a method closure. But, to me, that will be a overkill as you will be changing it at the Class level every time you need to modify the behavior on the instance. I would rather try to use metaClass on the instance if possible. – dmahapatro Jan 06 '14 at 20:53
  • Didn't know about the per instance metaclass. Thats the key: http://groovy.codehaus.org/Per-Instance+MetaClass. You get the bounty, but I might be nice for the next guy to summarize this in the answer to save time reading comment :-) – Dave Jan 07 '14 at 03:08
  • Actually, @tim_yates has mentioned the same thing(per instance metaclass) in his answer a long time ago. I just added my 2 cents to answer the comment (**I am trying to avoid modifying the original class**) to his answer as a way around, believing the bounty was offered to answer the same comment. :) – dmahapatro Jan 07 '14 at 03:15
1

I'm sure your still having this issue ;) but... I'm running Groovy version 1.8.7 on Fedora 17. I've found that have you have to do this combination:

A.metaClass.abc = {-> println it}
A obj = new A()
obj.metaClass.abc = {-> println it}
obj.abc

From there on out it will act as you want. When you look for the methods, you'll still get two:

Method org.codehaus.groovy.runtime.metaclass.ClosureMetaMethod@103074e[name: abc params: [] returns: class java.lang.Object owner: class A]
Method public void A.abc()

But at least you don't have to change your public void declaration.
Not sure if this is a bug or what.

Dan
  • 43
  • 6
0

If you just have the instance at runtime:

// save original method
def savedAbcMetaMethod = A.metaClass.getMetaMethod('abc', [] as Class[])

// Override Abc Method
A.metaClass.abc = {
    // Do something else here
    // ...
    // Method is called on instance (provide context with delegate)
    savedAbcMetaMethod.invoke(delegate)
    // ...
    // Perhaps more code here
}

MetaMethod on Groovy docs.

I derived this answer from here.

Maicon Mauricio
  • 2,052
  • 1
  • 13
  • 29