Clojure:从String类名创建新实例

chr*_*ris 17 constructor clojure

在Clojure中,给定一个类名作为字符串,我需要创建一个新的类实例.换句话说,我如何在中实现new-instance-from-class-name

(def my-class-name "org.myorg.pkg.Foo")
; calls constructor of org.myorg.pkg.Foo with arguments 1, 2 and 3
(new-instance-from-class-name  my-class-name 1 2 3) 
Run Code Online (Sandbox Code Playgroud)

我正在寻找一个更优雅的解决方案

  • 从类中构造函数调用Java newInstance方法
  • 使用eval,load-string,...

在实践中,我将在使用defrecord创建的类上使用它.因此,如果该场景有任何特殊语法,我会非常感兴趣.

Cho*_*ser 24

有两种很好的方法可以做到这一点.哪个最好取决于具体情况.

首先是反思:

(clojure.lang.Reflector/invokeConstructor
  (resolve (symbol "Integer"))
  (to-array ["16"]))

这就像调用(new Integer "16") ...包括你在to-array向量中需要的任何其他ctor参数.这很容易,但在运行时比使用new足够类型的提示要慢.

第二种选择是尽可能快,但有点复杂,并使用eval:

(defn make-factory [classname & types]
  (let [args (map #(with-meta (symbol (str "x" %2)) {:tag %1}) types (range))]
    (eval `(fn [~@args] (new ~(symbol classname) ~@args)))))

(def int-factory (make-factory "Integer" 'String))

(int-factory "42")

关键是eval代码定义匿名函数,也是make-factory如此.这很 - 比上面的反射例子慢,所以只能尽可能不频繁地进行,例如每班一次.但是这样做了你有一个常规的Clojure函数,你可以存储在某个地方,在int-factory这个例子中的var 中,或者在哈希映射或向量中,具体取决于你将如何使用它.无论如何,这个工厂函数将以完全编译的速度运行,可以由HotSpot等内联,并且总是比反射示例运行得快得多.

当你专门处理由deftypeor 生成的类时defrecord,你可以跳过类型列表,因为这些类总是只有两个ctors,每个ctors有不同的arities.这允许类似于:

(defn record-factory [recordname]
  (let [recordclass ^Class (resolve (symbol recordname))
        max-arg-count (apply max (map #(count (.getParameterTypes %))
                                      (.getConstructors recordclass)))
        args (map #(symbol (str "x" %)) (range (- max-arg-count 2)))]
    (eval `(fn [~@args] (new ~(symbol recordname) ~@args)))))


(defrecord ExampleRecord [a b c])

(def example-record-factory (record-factory "ExampleRecord"))

(example-record-factory "F." "Scott" 'Fitzgerald)