Существует два основных подхода:
Отражение:
(clojure.lang.Reflector/invokeConstructor Klass (to-array [arg ...]))
Медленно, но полностью динамично.
Предварительно распакуйте аргументы:
(let [[arg1 arg2 ...] args]
(Klass. arg1 arg2 ...))
((Klass. ...)
- идиоматический способ записи (new Klass ...)
; он преобразуется в последнюю форму во время расширения макроса.)
Это будетбыстрее, если компилятор может определить, какой конструктор будет использоваться (вам, вероятно, потребуется предоставить соответствующие подсказки типов - используйте (set! *warn-on-reflection* true)
, чтобы убедиться, что вы правильно поняли).
Второй подход, конечно, немного громоздкий.Если вы планируете создавать множество Klass
экземпляров во многих местах вашего кода, вы можете написать соответствующую фабричную функцию.Если вы собираетесь работать со многими классами таким образом, вы можете абстрагироваться от процесса определения фабричных функций:
(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))))
Наконец, если сам процесс определения фабричных функций должен быть полностью динамичным, вы можетесделайте что-то вроде второго подхода Чузера к этому вопросу : определите функцию, а не макрос и задайте ей eval
что-то вроде приведенной выше формы синтаксиса (defn ...)
(синтаксис-цитирования = с обратным ключом передэто; я не уверен, как включить буквальный обратный удар в SO-сообщение), за исключением того, что вы захотите использовать fn
вместо defn
и, возможно, пропустить fname
.Вызов компилятору будет дорогостоящим, но возвращаемая функция будет работать так же, как и любая функция Clojure;см. вышеупомянутый ответ Chouser для более продолжительного обсуждения.
Ради полноты, если вы используете Clojure 1.3 или более позднюю версию, а задействованный Java-класс на самом деле является записью Clojure, то позиционная фабричная функция будетуже были созданы под именем ->RecordName
.