从非类名符号访问类的静态字段

sku*_*uro 5 java interop clojure

这是 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]
Run Code Online (Sandbox Code Playgroud)

如何使用非类名、用户定义的符号访问 Java 类的静态方法/字段?

更新

以下按预期工作:

user> (eval (list (symbol (.getName y) "parseInt") "1"))
1
Run Code Online (Sandbox Code Playgroud)

有没有更好/更惯用的方法来达到相同的结果?

rae*_*aek 2

如果在编译时无法确定类(可能在宏中以编程方式),则需要诉诸于使用反射。这将与 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"]))
Run Code Online (Sandbox Code Playgroud)

这可以在运行时以任意方式使用,因为它不是宏或特殊形式。例如,方法的名称可以由用户在运行时通过 GUI 或套接字给出。

如果您在编译时知道类的名称,则可以按照尼古拉斯的建议使用宏。但是,没有必要将代码构造为看起来像(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))
Run Code Online (Sandbox Code Playgroud)

然而,该宏执行的唯一“实际工作”是将字符串转换为符号。您可能只.在外部宏中使用特殊形式(以某种方式获取方法名称的宏,例如通过将这些方法作为参数传递,或者从 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)
Run Code Online (Sandbox Code Playgroud)