I know I can instantiate a Java class like this in Clojure:
(new Classname args*)
Suppose I'm handed a list of the args that the constructor uses. How do I instantiate the class then? I can't use apply
since new
isn't a function.
There are two basic approaches:
Reflection:
(clojure.lang.Reflector/invokeConstructor Klass (to-array [arg ...]))
Slow, but completely dynamic.
Unpack the arguments beforehand:
(let [[arg1 arg2 ...] args]
(Klass. arg1 arg2 ...))
((Klass. ...)
is the idiomatic way of writing (new Klass ...)
; it gets converted to the latter form at macro expansion time.)
This will be faster if the compiler can deduce which constructor will be used (you'll probably need to provide appropriate type hints -- use (set! *warn-on-reflection* true)
to see if you've got it right).
The second approach is, of course, slightly unwieldy. If you expect to construct a lot of Klass
instances at a lot of places in your code, you can write an appropriate factory function. If you expect to deal with many classes in this way, you can abstract away the process of defining factory functions:
(defmacro deffactory [fname klass arg-types]
(let [params (map (fn [t]
(with-meta (gensym) {:tag t}))
arg-types)]
`(defn ~(with-meta fname {:tag klass}) ~(vec params)
(new ~klass ~@params))))
Finally, if the process of defining factory functions itself needs to be completely dynamic, you can do something like Chouser's second approach to this question: define a function rather than a macro and have it eval
something like the syntax-quoted (defn ...)
form above (syntax-quoted = with backtick in front of it; I'm not sure how to include a literal backtick in an SO post), except you'll want to use fn
rather than defn
and possibly skip the fname
. The call to the compiler will be expensive, but the function returned will perform as any Clojure function would; see the aforementioned answer by Chouser for a slightly longer discussion.
For the sake of completeness, if you're using Clojure 1.3 or later and the Java class involved is actually a Clojure record, then a positional factory function will have already been created under the name of ->RecordName
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With