Invoking Java method through method name as a String

107 views
Skip to first unread message

Richard Lyman

unread,
Feb 21, 2009, 2:31:55 AM2/21/09
to clo...@googlegroups.com
I have an instance of the Java class in a variable.
I have the method arguments in a vector.
I have the method name as a String.

I've tried so many different ways to invoke that method on that class and pass those parameters. I've tried macros, reflection, and read/eval. None of the ways I've tried feel right, and none of them have worked so far. Rather than dump all the failed code, here's some pseudo-code that I wish worked...

(prn "Result: "
       (apply (memfn-from-string "nameOfMethod")
                 class-instance
                 [arg1 arg2 arg3]))

Any pointers? What am I not seeing? Am I wanting too much?

Thanks!
-Richard

pmf

unread,
Feb 21, 2009, 8:10:09 AM2/21/09
to Clojure
You can old plain old Java-reflection:

;; get a reference to the method you need
(def method (.getMethod (Class/forName "java.lang.String")
"toUpperCase" nil))

;; reflectively call the method
(.invoke method "foo" nil)

Timothy Pratley

unread,
Feb 21, 2009, 8:15:00 AM2/21/09
to Clojure
Hi Richard,

As you probably know, Clojure java interop requires the method to be a
symbol:
user=> (. 5 toString)
"5"

So if you want to invoke a method from a string, you can convert the
string to symbol first:
user=> (symbol "toString")
toString

Great! but (. 5 (symbol "toString")) wont work because (symbol
"toString") is not evaluated. Instead it gets transformed into
5.symbol("toString") which is not what we want :( :(
However with a little trickery we can still do it (please excuse
ugliness - my macro-fu is weak):

(defmacro my-invoke [method-str instance & args]
`(. ~instance ~(symbol method-str) ~@args))

user=> (my-invoke "toString" 5)
"5"



Regards,
Tim.

Richard Lyman

unread,
Feb 21, 2009, 2:11:08 PM2/21/09
to clo...@googlegroups.com
Thanks for the quick response! :-)

This works fine when the method name is not in a var, but if you try:

user=> (defmacro my-invoke [method-str instance & args]

 `(. ~instance ~(symbol method-str) ~@args))
nil

user=> (my-invoke "toString" 5)
"5"
user=> (def command "toString")
#'user/command
user=> (my-invoke command 5)
java.lang.IllegalArgumentException: No matching field found: command for class java.lang.Integer (NO_SOURCE_FILE:0)
user=>


The macro is processed before the expression is eval'd - right? That's why the call to symbol inside the macro returns 'command' as the method you're invoking - right?

I'm not sure I'm understanding everything here, but it seems like I'm needing a way to delay the evaluation of that part of the macro until the value bound to the command var can be inserted...

-Richard

Timothy Pratley

unread,
Feb 21, 2009, 6:19:30 PM2/21/09
to Clojure
How embarrassing!

This works much better:

(defn str-invoke [method-str instance & args]
(clojure.lang.Reflector/invokeInstanceMethod instance method-str (to-
array args)))

(let [ts "toString", ct "compareTo"]
(println (str-invoke ts 5))
(println (str-invoke ct 5 4))
(println (str-invoke ct 5 5)))

ie: this is just using reflection as pmf suggested, however I think
this form is more convenient as Clojure's Reflector class does all the
hard work of matching the parameters for you. I'm sure someone will
post a macro solution, but hopefully this will do till then.


Regards,
Tim.

Richard Lyman

unread,
Feb 21, 2009, 9:44:45 PM2/21/09
to clo...@googlegroups.com
Cool... that looks like it got me past the 'method-as-a-string' problem, but now I'm getting errors with not finding a matching method on the class by that name. I'm guessing that this means there was a problem matching the type signature of the method.

The arguments to the method are stored in a 'clojure.lang.PersistentVector', and if I duplicate the method I'm trying to access but set the only parameter to be typed as 'clojure.lang.PersistentVector', then it works. The new method with the new type signature works. Which to me means that we're passing all the arguments to the method still inside their original vector - right?

I guess I'm still stuck on how to expand the vector of arguments in place... and I'm really not very sure what you're doing with the '&' in the parameters for the str-invoke. Is that a way of slurping all the remaining parameters into a vector? If so, then aren't I needing to do the opposite?

I tried removing the &, and into-array - that didn't work. I tried changing the method into a macro so I could splice in the vector of args - that didn't work. This is getting frustrating. Everything else has been so relatively easy to do until now.

I really appreciate your help in walking me through my lack of understanding.

If I'm following you correctly I think we're here:

user=> (defn str-invoke [instance method-str & args] (clojure.lang.Reflector/invokeInstanceMethod instance method-str (into-array args)))
#'user/str-invoke
user=> (def i "sampleString")
#'user/i
user=> (def m "substring")
#'user/m
user=> (def args [2,3])
#'user/args
user=> (str-invoke i m args)
java.lang.IllegalArgumentException: Unexpected param type (NO_SOURCE_FILE:0)
user=> (str-invoke i m 2 3)
"m"
user=>

-Rich

Michael Wood

unread,
Feb 22, 2009, 2:41:23 AM2/22/09
to clo...@googlegroups.com
On Sun, Feb 22, 2009 at 4:44 AM, Richard Lyman <richar...@gmail.com> wrote:
[...]

> user=> (defn str-invoke [instance method-str & args]
> (clojure.lang.Reflector/invokeInstanceMethod instance method-str (into-array
> args)))
> #'user/str-invoke
> user=> (def i "sampleString")
> #'user/i
> user=> (def m "substring")
> #'user/m
> user=> (def args [2,3])
> #'user/args
> user=> (str-invoke i m args)
> java.lang.IllegalArgumentException: Unexpected param type (NO_SOURCE_FILE:0)
> user=> (str-invoke i m 2 3)
> "m"
> user=>

user=> (apply str-invoke i m args)
"m"

--
Michael Wood <esio...@gmail.com>

Timothy Pratley

unread,
Feb 22, 2009, 3:13:01 AM2/22/09
to Clojure
> I guess I'm still stuck on how to expand the vector of arguments in place...

One way is (apply function vector) -> (function v1 v2 v3)


> and I'm really not very sure what you're doing with the '&' in the
> parameters for the str-invoke. Is that a way of slurping all the remaining
> parameters into a vector? If so, then aren't I needing to do the opposite?

Yup precisely :)


> I tried removing the &, and into-array - that didn't work.

Close! Just removing & but leave into-array is what you want.
(defn str-invoke [instance method-str args]
(clojure.lang.Reflector/invokeInstanceMethod
instance method-str (to-array args)))
user=> (str-invoke i m args)
"m"


So in summary you can either use apply or remove the &


Regards,
Tim.

Richard Lyman

unread,
Feb 22, 2009, 11:06:10 AM2/22/09
to clo...@googlegroups.com
This is _so_ awesome!!

Thanks a ton for your patient help Tim, and others.

In the end I also had to switch the use of into-array to to-array, since into-array expects all the elements to be the same type, and my args were of varying types. Using to-array worked since it cast each element to the Object type before passing it along.

Again, thanks a ton. I'd been working for quite a while on trying to get this knot tied off.

I'm going to make a page on the Wiki about this, hopefully no one else will need to go through this again. :-)

-Rich

Richard Lyman

unread,
Feb 22, 2009, 11:21:21 AM2/22/09
to clo...@googlegroups.com
Reply all
Reply to author
Forward
0 new messages