12

In Clojure, how to use a java Class that is stored in a variable?

How should I fix the following code?

(def a java.lang.String)
(new a "1"); CompilerException java.lang.IllegalArgumentException: Unable to resolve classname: a

And why this one works fine?

(def a str)
(a "1")
1
  • 3
    I thought this has come up before and indeed it has: see Clojure: creating new instance from String class name with a great answer by Chouser which mentions both clojure.lang.Reflector/invokeConstructor and another approach, a sort of middle ground between "static+fast" and "dynamic+slow" (you could call it "very dynamic+slow once, static+fast later"), which may well be of interest to you. Commented Feb 17, 2012 at 0:08

3 Answers 3

12

The most elegant solution is to write construct that does the same as new but is able to receive a class dynamically:

 (defn construct [klass & args]
    (clojure.lang.Reflector/invokeConstructor klass (into-array Object args)))
 (def a HashSet)
 (construct a '(1 2 3)); It works!!!

This solution overcomes the limitation of @mikera's answer (see comments).

Special Thanks to @Michał Marczyk that made me aware of invokeConstructor answering another question of mine: Clojure: how to create a record inside a function?.

Another option is to store the call to the constructor as an anonymous function. In our case:

(def a #(String. %1))
(a "111"); "111"
Sign up to request clarification or add additional context in comments.

Comments

7

When you define a in this way, you get a var containing a java.lang.Class

(def a java.lang.String)

(type a)
=> java.lang.Class

You then have 2 options:

A: Construct the new instance dynamically by finding the Java constructor using the reflection API. Note that as Yehonathan points out you need to use the exact class defined in the constructor signature (a subclass won't work as it won't find the correct signature):

(defn construct [klass & args]
  (.newInstance
    (.getConstructor klass (into-array java.lang.Class (map type args)))
    (object-array args)))

(construct a "Foobar!")
=> "Foobar!"

B: Construct using Clojure's Java interop, which will require an eval:

(defn new-class [klass & args]
  (eval `(new ~klass ~@args)))

(new-class a "Hello!")
=> "Hello!"

Note that method A is considerably faster (about 60x faster on my machine), I think mainly because it avoids the overhead of invoking the Clojure compiler for each eval statement.

4 Comments

There is an issue with construct when passing as argument a derived class of the class defined in the signature. For instance (construct HashSet '(8)) causes an exception while (new HashSet '(8)) doesn't.
Option B: looks fine. Are there any limitations/concerns due to the usage of eval?
Potential downsides of eval: Watch out for externally-provided values - there could be a code injection security risk. Also eval has some additional overhead as it invokes the Clojure compiler. It can also lead to some "clever" code that is tricky to maintain if you are not careful. In general eval is fine if used with caution.
Have a look at my answer below: it overcomes the limitation of Option B.
6

The problem is that Clojure implements Java interop using a number of special forms:

user=> (doc new)
-------------------------
new
Special Form
  Please see http://clojure.org/special_forms#new
nil

this basically means the "normal" Clojure syntax is altered to allow for handier constructs when calling Java. As a naive reflection solution to your dynamic Java needs, you can leverage eval:

user=> (def a String) ; java.lang package is implicitly imported
#'user/a
user=> `(new ~a "test") ; syntax quote to create the correct form
(new java.lang.String "test")
user=> (eval `(new ~a "test")) ; eval to execute
"test"

The same strategy works with all the other interop special forms, like method invocation.


EDIT: look also at the answer from @mikera for a more performing alternative via the Java reflection API.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.