В 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.