【问题标题】:In Clojure, how to use a java Class dynamically?在 Clojure 中,如何动态使用 java 类?
【发布时间】:2012-02-06 21:23:09
【问题描述】:

在Clojure中,如何使用存储在变量中的java类?

我应该如何修复以下代码?

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

为什么这个可以正常工作?

(def a str)
(a "1")

【问题讨论】:

  • 我认为这之前已经出现过,并且确实出现过:参见 Clojure: creating new instance from String class name Chouser 的一个很好的回答,其中提到了 clojure.lang.Reflector/invokeConstructor 和另一种方法,一种介于“静态+快速”之间的中间立场"和“动态+慢”(您可以称其为“非常动态+慢一次,静态+快”),这可能是您感兴趣的。

标签: clojure


【解决方案1】:

最优雅的解决方案是编写与new 相同但能够动态接收类的construct

 (defn construct [klass & args]
    (clojure.lang.Reflector/invokeConstructor klass (into-array Object args)))
 (def a HashSet)
 (construct a '(1 2 3)); It works!!!

这个解决方案克服了@mikera的回答的限制(见cmets)。

特别感谢@Michał Marczyk 让我知道invokeConstructor 回答了我的另一个问题:Clojure: how to create a record inside a function?

另一种选择是将对构造函数的调用存储为匿名函数。在我们的例子中:

(def a #(String. %1))
(a "111"); "111"

【讨论】:

    【解决方案2】:

    当你以这种方式定义 a 时,你会得到一个包含 java.lang.Class 的 var

    (def a java.lang.String)
    
    (type a)
    => java.lang.Class
    

    你有两个选择:

    A:通过使用反射 API 查找 Java 构造函数来动态构造新实例。请注意,正如 Yehonathan 指出的,您需要使用构造函数签名中定义的 exact 类(子类将无法工作,因为它找不到正确的签名):

    (defn construct [klass & args]
      (.newInstance
        (.getConstructor klass (into-array java.lang.Class (map type args)))
        (object-array args)))
    
    (construct a "Foobar!")
    => "Foobar!"
    

    B:使用 Clojure 的 Java 互操作构建,这将需要一个 eval:

    (defn new-class [klass & args]
      (eval `(new ~klass ~@args)))
    
    (new-class a "Hello!")
    => "Hello!"
    

    请注意,方法 A 相当快(在我的机器上大约快 60 倍),我认为主要是因为它避免了为每个 eval 语句调用 Clojure 编译器的开销。

    【讨论】:

    • 在将签名中定义的类的派生类作为参数传递时,construct 存在问题。例如(construct HashSet '(8)) 会导致异常,而(new HashSet '(8)) 不会。
    • 选项 B:看起来不错。使用eval 是否有任何限制/顾虑?
    • eval 的潜在缺点:注意外部提供的值 - 可能存在代码注入安全风险。 eval 在调用 Clojure 编译器时也有一些额外的开销。如果您不小心,它还可能导致一些难以维护的“聪明”代码。一般来说,如果谨慎使用 eval 是可以的。
    • 看看我下面的答案:它克服了选项 B 的限制。
    【解决方案3】:

    问题在于 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"
    

    相同的策略适用于所有其他互操作特殊形式,例如method invocation


    编辑:通过 Java 反射 API 查看来自 @mikeraanswer 以获得更高性能的替代方案。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-01-20
      • 2011-08-28
      • 1970-01-01
      相关资源
      最近更新 更多