28

I know in Groovy you can invoke a method on a class/object using a string. For example:

Foo."get"(1)
  /* or */
String meth = "get"
Foo."$meth"(1)

Is there a way to do this with the class? I have the name of the class as a string and would like to be able to dynamically invoke that class. For example, looking to do something like:

String clazz = "Foo"
"$clazz".get(1)

I think I'm missing something really obvious, just am not able to figure it out.

cdeszaq
  • 30,869
  • 25
  • 117
  • 173
John Wagenleitner
  • 10,967
  • 1
  • 40
  • 39
  • classes dont get "invoked" - only methods. What is it exactly that you want to invoke? do you want to do something like MyOwnClass.static_property()? or myInstanceOfClass.methodName()? – Chii Feb 23 '09 at 12:49
  • 1
    My guess is that he wants to invoke a static method on a class. – Aaron Digulla Feb 23 '09 at 12:52
  • I want to invoke a static method on a class, a class I that I don't know until run time. I know the Java way is to use Class.forName, was just curious if there was a Groovy way to do this like their is for methods. – John Wagenleitner Feb 23 '09 at 16:18

6 Answers6

31

As suggested by Guillaume Laforge on Groovy ML,

("Foo" as Class).get(i)

would give the same result.

I've tested with this code:

def name = "java.lang.Integer"
def s = ("$name" as Class).parseInt("10")
println s
chanwit
  • 3,174
  • 2
  • 23
  • 20
17

Try this:

def cl = Class.forName("org.package.Foo")
cl.get(1)

A little bit longer but should work.

If you want to create "switch"-like code for static methods, I suggest to instantiate the classes (even if they have only static methods) and save the instances in a map. You can then use

map[name].get(1)

to select one of them.

[EDIT] "$name" is a GString and as such a valid statement. "$name".foo() means "call the method foo() of the class GString.

[EDIT2] When using a web container (like Grails), you have to specify the classloader. There are two options:

Class.forName("com.acme.MyClass", true, Thread.currentThread().contextClassLoader)

or

Class.forName("com.acme.MyClass", true, getClass().classLoader)

The first option will work only in a web context, the second approach also works for unit tests. It depends on the fact that you can usually use the same classloader as the class which invokes forName().

If you have problems, then use the first option and set the contextClassLoader in your unit test:

def orig = Thread.currentThread().contextClassLoader
try {
    Thread.currentThread().contextClassLoader = getClass().classLoader

    ... test ...
} finally {
    Thread.currentThread().contextClassLoader = orig
}
Aaron Digulla
  • 321,842
  • 108
  • 597
  • 820
  • I was hoping to find out if there was a "Groovy" way of doing Class.forName, similar to how they made reflection on methods so easy. I do appreciate your answer and suspect that this might be the only way. – John Wagenleitner Feb 23 '09 at 16:20
  • @John: No, that's not possible. I asked on the Groovy ML. You have to call Class.forName() because "$name" is a GString and Groovy doesn't try to be smart about the contents of the variable "name". – Aaron Digulla Feb 23 '09 at 16:37
  • @Aaron - thanks, I read the thread on the Groovy ML and that just the info I was looking for. – John Wagenleitner Feb 24 '09 at 00:58
  • It doesn't work with classes defined in the current script, does it? class TestName { } Class.forName("TestName") – Mykola Golubyev Dec 14 '09 at 10:20
  • 1
    Sure. Why not? The script is read, compiled and then run. At the time when it is run, all the class files exist, so Class.forName() can find them. – Aaron Digulla Dec 14 '09 at 15:21
  • 1
    With Grails domain classes, you will get a ClassNotFoundException unless you manually specify the correct class loader: `Class.forName("com.acme.MyClass", true, Thread.currentThread().contextClassLoader)` - thanks for [BinaryBuffer](http://binarybuffer.com/2012/03/grails-class-forname-classnotfoundexception) for the tip. – miek Apr 30 '13 at 05:55
  • @miek: Thanks, I've improved my answer. – Aaron Digulla Apr 30 '13 at 07:06
3

An augmentation to Chanwit's answer illustrating creation of an instance:

def dateClass = 'java.util.Date' as Class
def date = dateClass.newInstance()
println date
Paul King
  • 627
  • 4
  • 6
2

Here's another way

import org.codehaus.groovy.grails.commons.ApplicationHolder as AH

def target = application.domainClasses.find{it.name == 'ClassName'}
target.clazz.invokeMethod("Method",args)

With this you don't need to specify the package name. Be careful though if you have the same class name in two different packages.

ken
  • 3,745
  • 6
  • 34
  • 49
  • I'm running Grails 1.2.1 and Groovy 1.6.3, and I was getting class not found exceptions using the ClassForName() and as Class casting methods above. This particular approach worked for me. – Steve Goodman Nov 04 '10 at 18:26
1

I'm running version 1.8.8 groovy... and the simple example works.

Import my.Foo
def myFx="myMethodToCall"
def myArg = 12

Foo."$myFx"(myArg)

Calls Foo.myMethodToCall(12) as expected and desired. I don't know if this has always been the case though.

alcoholiday
  • 719
  • 5
  • 10
1

Melix on Groovy ML pointed me in the "right" direction on dynamic class method invokation awhile back, quite useful:

// define in script (not object) scope  
def loader = this.getClass().getClassLoader()

// place this in some MetaUtils class, invoked on app startup  
String.metaClass.toClass = {  
    def classPath = getPath(delegate) // your method logic to determine 'path.to.class'
    Class.forName(classPath, true, this.loader)  
}

// then, anywhere in your app  
"Foo".toClass().bar()

You could create another string metaClass method to create instances as well, refactoring as appropriate:

String.metaClass.toObject = {  
    def classPath = getPath(delegate)  
    Class.forName(classPath, true, this.loader).newInstance()  
}

Groovy is pure fun ;--)

Kirk Woll
  • 76,112
  • 22
  • 180
  • 195
virtualeyes
  • 11,147
  • 6
  • 56
  • 91