Calling static methods on a variable holding a class

466 views
Skip to first unread message

Nicolas Buduroi

unread,
Jul 6, 2009, 6:59:45 PM7/6/09
to Clojure
Hi, I needed to call a static method on a class stored in a var
yesterday and found that it was a little bit trickier than I initially
thought. There's three way of doing it, the two first are quite
straightforward and working ;-) e.g.:

(import '(java.nio ByteBuffer FloatBuffer))

(def foo ByteBuffer)

(. foo (allocate 1024)) ; throw an exception as intended.

(defmacro allocate1 [buffer-type size]
`(. ~(eval buffer-type) (allocate ~size)))

(defn allocate2 [buffer-type size]
(eval `(. ~buffer-type (allocate ~size))))

(allocate1 foo 1024)
(allocate2 foo 1024)

Both works fine, but I'm still not sure which one is the best. The
third way is to call a static method from the class object, I've tried
something but wasn't able to get it working. It throws some weird
exception, here's the code:

(defn to-primitive [class]
(try
(let [type (.getField (identity class) "TYPE")]
(.get type Object))
(catch NoSuchFieldException _ class)))

(defmacro call-static [class name & args]
(let [arg-types (into-array
(map (comp to-primitive #(.getClass %)) args))
method (.getMethod (eval class) name arg-types)]
`(.invoke ~method ~class ~@args)))

When calling this macro (call-static foo "allocate" 1024) it throws:
java.lang.RuntimeException: Can't embed object in code, maybe print-
dup not defined: public static java.nio.ByteBuffer
java.nio.ByteBuffer.allocate(int) (NO_SOURCE_FILE:0)

This error message is quite puzzling isn't it?

Is there any other way?

Thanks

- budu

Nicolas Buduroi

unread,
Jul 6, 2009, 8:16:45 PM7/6/09
to Clojure
I've just figured out that the macro version in the allocate example
can't be used with local variables.

(let [foo ByteBuffer]
(allocate1 foo 1024))

throws java.lang.UnsupportedOperationException: Can't eval locals
(NO_SOURCE_FILE:94)

Adrian Cuthbertson

unread,
Jul 6, 2009, 11:21:52 PM7/6/09
to clo...@googlegroups.com
You can call the static method directly on the class name;

(java.nio.ByteBuffer/allocate 1024)

or just (ByteBuffer/allocat 1024)
if it's imported.

Rgds, Adrian.

Adrian Cuthbertson

unread,
Jul 6, 2009, 11:53:00 PM7/6/09
to clo...@googlegroups.com
Hi Nicolas, sorry, that last post missed the second part, I meant to add;

If you know the method you wish to call, do you not know the class and can thus call the static method directly?

-Adrian.

Stuart Sierra

unread,
Jul 7, 2009, 3:29:40 PM7/7/09
to Clojure
On Jul 6, 6:59 pm, Nicolas Buduroi <nbudu...@gmail.com> wrote:
> Hi, I needed to call a static method on a class stored in a var
> yesterday and found that it was a little bit trickier than I initially
> thought.

My first impression is that this is probably not the best way to go
about this. Java classes are not like Ruby or Python classes; you
can't just call methods on them. Using "eval" is a hack, and probably
not a good idea.

If you can determine, in your code, which class is needed, then do
that and call the static method directly using the (ClassName/
methodName...) syntax.

If you really don't know what the class is (for example, you get a
Class object returned by some library function) then you can use the
Java Reflection API to call the static method. See
http://java.sun.com/docs/books/tutorial/reflect/

-SS

Stephen C. Gilardi

unread,
Jul 7, 2009, 3:45:05 PM7/7/09
to clo...@googlegroups.com

On Jul 7, 2009, at 3:29 PM, Stuart Sierra wrote:

> If you really don't know what the class is (for example, you get a
> Class object returned by some library function) then you can use the
> Java Reflection API to call the static method. See
> http://java.sun.com/docs/books/tutorial/reflect/

If you go this route, I recommend either using or learning from the
functions in clojure/src/jvm/clojure/lang/Reflector.java. Its methods
are not part of Clojure's official interface, so the usual caveats
about using unsupported "implementation private" code apply, but it
include many useful methods for dealing with reflection from Clojure.

--Steve

Nicolas Buduroi

unread,
Jul 8, 2009, 10:23:55 AM7/8/09
to Clojure
> If you know the method you wish to call, do you not know the class and can
> thus call the static method directly?

Well that was the point of the question, that is if I have to call a
static method on a class we don't know in advance. I understand this
capability isn't that useful and is quite rarely used, but maybe
somebody will one day need it for some practical reason. ;-)

> My first impression is that this is probably not the best way to go
> about this. Java classes are not like Ruby or Python classes; you
> can't just call methods on them. Using "eval" is a hack, and probably
> not a good idea.

Yeah, you're quite right about this. For now I'm just experimenting
with some ideas on how to abstract Java libraries, nio in this case.
So it's not something I really need.

> If you go this route, I recommend either using or learning from the  
> functions in clojure/src/jvm/clojure/lang/Reflector.java. Its methods  
> are not part of Clojure's official interface, so the usual caveats  
> about using unsupported "implementation private" code apply, but it  
> include many useful methods for dealing with reflection from Clojure.

I'll certainly use the Reflector class, should have thought about it!
I wonder if it would be a good idea to include a function for it in
Clojure or in Contrib?

As for the call-static macro, I now understand the error message. It
was quite obvious in fact, as macros must output something that the
reader can read!

Thanks all!

- budu

Chouser

unread,
Jul 8, 2009, 11:37:30 AM7/8/09
to clo...@googlegroups.com
On Wed, Jul 8, 2009 at 10:23 AM, Nicolas Buduroi<nbud...@gmail.com> wrote:
>
>> If you know the method you wish to call, do you not know the class and can
>> thus call the static method directly?
>
> Well that was the point of the question, that is if I have to call a
> static method on a class we don't know in advance. I understand this
> capability isn't that useful and is quite rarely used, but maybe
> somebody will one day need it for some practical reason. ;-)

Lets say you want to call static method "foo" of a class,
but you don't know which class -- you want this to be
specified at runtime in a parameter. Something like this:

(defn map-foo [cls coll]
(map cls/foo coll)) ; doesn't work

As mentioned by others, one approach is to use reflection,
either Java's API or Clojure's (undocumented,
may-change-without-warning) wrapper:

(defn map-foo [cls coll]
(map #(clojure.lang.Reflector/invokeStaticMethod
cls "foo" (to-array [%]))
coll))

(map-foo MyClass [1 2 3 4])

This works, but if you're allowed to adjust the requirements
a bit, a better solution is possible. What if your user
called:

(map-foo #(MyClass/foo %) [1 2 3 4])

This can be done without any runtime reflection at all,
which can greatly boost the runtime speed. Also, it's more
flexible: I *told* you to name your method "foo" but
I guess I should have been more explicit. Oh well, no
matter:

(map-foo #(YourClass/Foo %) [1 2 3 4])

Or if your user is using Clojure rather than Java, they
don't even have to use a class:

(map-foo #(her-do-foo-thing %) [1 2 3 4])

or just:

(map-foo her-do-foo-thing [1 2 3 4])

Best of all implementing map-foo is simpler:

(defn map-foo [f coll]
(map f coll))

Which in this trivial example means you can just use 'map'
instead of 'map-foo'. But even in a less trivial example
you may find this approach more workable all around.

Now if you were going to do more than one thing on the class
(call static methods foo, bar, and baz, for example) things
get more complicated. You may be able to pass around a map
of fns, perhaps assisted by a macro. ...or you may decide
that reflection is better after all.

(map-things {:foo #(MyClass/foo %)
:bar #(MyClass/bar %)
:baz #(MyClass/baz %)}
coll)

(map-things (static-method-map MyClass [foo bar baz]) coll)

--Chouser

Nicolas Buduroi

unread,
Jul 8, 2009, 1:57:30 PM7/8/09
to Clojure
Wow, that was a great answer, a thousand thanks!!!!

> Now if you were going to do more than one thing on the class
> (call static methods foo, bar, and baz, for example) things
> get more complicated.

Yeah, that was one of constraint for the problem I was trying to
solve, I should have mentioned it.

- budu
Reply all
Reply to author
Forward
0 new messages