【问题标题】:Accessing static fields of a class from a non-Classname symbol从非类名符号访问类的静态字段
【发布时间】:2011-04-28 08:07:58
【问题描述】:

这是 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 类的静态方法/字段?

更新

以下按预期工作:

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

是否有更好/更惯用的方法来实现相同的结果?

【问题讨论】:

  • 你能解释一下你为什么要这样做吗?
  • 我必须应对设计不佳的 Java API,其中没有方法的基本接口由几个其他接口扩展,每个接口提供一个方法。这些方法旨在通过具体实现注册为回调,这是通过反射完成的(例如Registry.registerCallback(ImplementedInterface/CONDITION, concreteInstance, "callbackMethodName"))。我想将所有的牦牛剃须代码隐藏在一个更温和的 Clojure API 后面,因此我需要动态绑定到一个接口并使用反射来执行回调注册。
  • eval 方法有一个偷偷摸摸的缺点:eval 不能在本地范围内使用变量;就好像它是在顶层执行的。因此, (let [x 1] (eval '(inc x))) 会导致“无法解析符号:x”错误。

标签: java interop clojure


【解决方案1】:

如果在编译期间无法确定类(可能在宏中以编程方式),则需要使用反射。这将与 eval 在尝试编译代码时执行相同的操作。见clojure.lang.Reflector/invokeStaticMethodhttps://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)

【讨论】:

    【解决方案2】:

    理论上可以采用以下方法:

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

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

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

    不保证;)

    【讨论】:

    • 感谢您提出的方法。但是,我用另一种更简洁的方式更新了原始问题。也有点丑,你觉得有什么可以改进的吗?
    【解决方案3】:

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

    【讨论】:

    • eval 在为静态调用生成字节码时使用 Clojure 的 Reflector 类。您不必使用 eval 执行此操作,因为此类还提供反射调用的方法:(clojure.lang.Reflector/invokeStaticMethod ...)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2016-03-17
    • 1970-01-01
    • 2016-11-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多