Доступ к статическим полям класса из символа не-Classname - PullRequest
4 голосов
/ 28 апреля 2011

Вот отрывок из сеанса REPL, который, надеюсь, объясняет, чего я хочу достичь:

user> (Integer/parseInt "1")
1
user> (def y Integer)
#'user/y
user> (y/parseInt "1")
No such namespace: y
  [Thrown class java.lang.Exception]

Как я могу получить доступ к статическому методу / полям класса Java, используя не-Classname, определенный пользователем символ?

ОБНОВЛЕНИЕ

Следующее работает, как ожидалось:

user> (eval (list (symbol (.getName y) "parseInt") "1"))
1

Есть ли лучший / более идиоматический способ достижения того же результата?

Ответы [ 3 ]

2 голосов
/ 29 апреля 2011

Если вы не можете определить класс (возможно, программно в макросе) во время компиляции, вам нужно прибегнуть к использованию отражения.Это будет делать то же самое, что и eval, когда он пытается скомпилировать код.См. clojure.lang.Reflector/invokeStaticMethod: https://github.com/clojure/clojure/blob/master/src/jvm/clojure/lang/Reflector.java#L198

(import 'clojure.lang.Reflector)
;; Here, you can pass *any string you have at runtime*
(Reflector/invokeStaticMethod Integer "parseInt" (into-array ["1"]))

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

Если у вас есть имя класса во время компиляции, вы можете использовать макрос, как предложил Николас,Однако нет необходимости создавать код, похожий на (Integer/parseInt "1"), поскольку это просто синтаксический сахар для более базовой (и дружественной к макросам) . специальной формы: (. Integer parseInt "1").

;; Here, the method name needs to be a *string literal*
(defmacro static-call
  "Takes a Class object, a string naming a static method of it
  and invokes the static method with the name on the class with
  args as the arguments."
  [class method & args]
  `(. ~class ~(symbol method) ~@args))

Однако,единственная «настоящая работа», которую выполняет этот макрос, - преобразовать строку в символ.Вы, вероятно, просто использовали бы специальную форму . во внешнем макросе (тот, который каким-то образом получает имена методов, например, получая те, которые передаются в качестве аргументов, или читая их из var или из файла конфигурации).

;; Use ordinary Clojure functions to construct this
(def the-static-methods {:foo ["Integer" "parseInt"], :bar ["Long" "parseLong"]})

;; Macros have access to all previously defined values
(defmacro generate-defns []
  (cons `do (for [[name-keyword [class-string method-string]] the-static-methods]
              `(defn ~(symbol (name name-keyword)) [x#]
                 (. ~(symbol class-string) ~(symbol method-string) x#)))))

(generate-defns)
0 голосов
/ 28 апреля 2011

Не думаю, что есть лучший способ, чем eval звонок, который вы предоставили.Вы всегда можете обернуть его в красивый макрос:

(defmacro static-call [var method & args]
  `(-> (.getName ~var)
       (symbol ~(str method))
       (list ~@args)
       eval))

Обновление : как подсказывает raek, вот версия с использованием класса Reflector:

(defmacro static-call [var method & args]
  `(clojure.lang.Reflector/invokeStaticMethod
    (.getName ~var)
    ~(str method)
    (to-array ~(vec args))))

Обратите внимание, что я написал макрос в обоих случаях только для удобства сохранения некоторых символов.Для лучшей производительности вы должны использовать invokeStaticMethod напрямую.

0 голосов
/ 28 апреля 2011

Теоретически может работать следующий подход:

Вы можете написать макрос-псевдоним, который позволяет вам сделать (def-псевдоним y Integer).Этот макрос должен:

  • определить пространство имен 'y' (create-ns ...)
  • найти все методы для Integer (или любого другого класса), используя (.getMethods...)
  • динамически создавать тонкие оболочки для всех этих методов в пространстве имен 'y'

Это довольно уродливо, поскольку этот подход также создаст оболочки для методов, которые вам не нужны.

Без гарантий;)

...