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



下面是一个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]

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

如下所示:

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

是否有更好/更习惯的方法来达到相同的结果?

如果在编译期间无法确定类(可能在宏中以编程方式),则需要使用反射。这将与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"]))

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

如果你在编译时有类的名字,你可以像Nicolas建议的那样使用宏。然而,没有必要把代码构造得像(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)

理论上,以下方法是可行的:

你可以写一个宏define -alias,允许你做(define -alias y Integer)。这个宏应该:

  • 定义一个命名空间'y' (create-ns…)
  • 查找Integer(或任何其他类)的所有方法,使用(。getMethods…)
  • 动态地为命名空间'y'中的所有方法创建瘦包装器

这有点难看,因为这种方法也会为你不需要的方法创建包装器。

不保证;)

我认为没有比您提供的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

最新更新