В Clojure, как использовать динамический класс Java?
В Clojure, как использовать класс java, который хранится в переменной?
Как мне исправить следующий код?
(def a java.lang.String)
(new a "1"); CompilerException java.lang.IllegalArgumentException: Unable to resolve classname: a
И почему это хорошо работает?
(def a str)
(a "1")
Ответы
Ответ 1
Самое элегантное решение - написать construct
, который делает то же самое, что и new
, но может динамически получать класс:
(defn construct [klass & args]
(clojure.lang.Reflector/invokeConstructor klass (into-array Object args)))
(def a HashSet)
(construct HashSet '(1 2 3)); It works!!!
Это решение преодолевает ограничение @mikera ответа (см. комментарии).
Особая благодарность @Michał Marczyk, которая сообщила мне о invokeConstructor
ответе на другой мой вопрос: Clojure: как создать запись внутри функции?.
Другой вариант - сохранить вызов конструктора как анонимную функцию. В нашем случае:
(def a #(String. %1))
(a "111"); "111"
Ответ 2
Когда вы определяете a таким образом, вы получаете var, содержащий java.lang.Class
(def a java.lang.String)
(type a)
=> java.lang.Class
У вас есть 2 варианта:
A: Создайте новый экземпляр динамически, найдя конструктор Java, используя API отражения. Обратите внимание, что, как указывает Йехонатхан, вам нужно использовать точный класс, определенный в сигнатуре конструктора (подкласс не будет работать, так как он не найдет правильную подпись):
(defn construct [klass & args]
(.newInstance
(.getConstructor klass (into-array java.lang.Class (map type args)))
(object-array args)))
(construct a "Foobar!")
=> "Foobar!"
B: Построить с помощью Clojure Java-взаимодействия, для которого потребуется eval:
(defn new-class [klass & args]
(eval `(new ~klass [email protected])))
(new-class a "Hello!")
=> "Hello!"
Обратите внимание, что метод A значительно быстрее (примерно на 60 раз быстрее на моей машине), я думаю, в основном потому, что он избегает накладных расходов при вызове компилятора Clojure для каждого оператора eval.
Ответ 3
Проблема заключается в том, что Clojure реализует Java-взаимодействие с использованием ряда специальных форм:
user=> (doc new)
-------------------------
new
Special Form
Please see http://clojure.org/special_forms#new
nil
это в основном означает, что "нормальный" Clojure синтаксис изменен, чтобы разрешить конструкцию handier при вызове Java. Как наивное решение для решения ваших динамических потребностей Java, вы можете использовать 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"
Та же стратегия работает со всеми другими специальными формами interop, такими как вызов метода.
EDIT: посмотрите также на от @mikera для более эффективной альтернативы с помощью отражения Java API.