В Clojure, как динамически использовать Java-класс? - PullRequest
10 голосов
/ 07 февраля 2012

Как использовать Clojure в Java-классе, который хранится в переменной?

Как мне исправить следующий код?

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

А почему этот работает нормально?

(def a str)
(a "1")

Ответы [ 3 ]

8 голосов
/ 07 февраля 2012

Самое элегантное решение - написать 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"
7 голосов
/ 08 февраля 2012

Когда вы определяете таким образом, вы получаете переменную, содержащую java.lang.Class

(def a java.lang.String)

(type a)
=> java.lang.Class

У вас есть 2 варианта:

A: Создайте новый экземпляр динамически, найдя конструктор Java с помощью API отражения. Обратите внимание, что, как указывает Yehonathan, вам нужно использовать класс точный , определенный в сигнатуре конструктора (подкласс не будет работать, поскольку он не найдет правильную сигнатуру):

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

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

B: Создайте, используя Java-взаимодействие Clojure, для которого потребуется eval:

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

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

Обратите внимание, что метод A значительно быстрее (примерно в 60 раз быстрее на моем компьютере), я думаю, в основном потому, что он позволяет избежать накладных расходов при вызове компилятора Clojure для каждого оператора eval.

6 голосов
/ 07 февраля 2012

Проблема в том, что Clojure реализует взаимодействие Java с использованием ряда специальных форм:

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

это в основном означает, что "нормальный" синтаксис Clojure изменен, чтобы учесть более удобные конструкции при вызове 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"

Та же стратегия работает со всеми другими специальными формами взаимодействия, такими как вызов метода .


РЕДАКТИРОВАТЬ: посмотрите также ответ из @ mikera для более эффективной альтернативы через API отражения Java.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...