Clojure:从String类名创建新实例

时间:2010-09-20 03:34:54

标签: 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) 

我正在寻找比

更优雅的解决方案
  • 在类
  • 的构造函数上调用Java newInstance方法
  • 使用eval,load-string,......

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

4 个答案:

答案 0 :(得分:23)

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

首先是反思:

(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,或者在哈希映射或向量中,具体取决于你将如何使用它。无论如何,这个工厂函数将以完全编译的速度运行,可以通过HotSpot等内联运行,并且总是比反射示例运行更快

当您专门处理由deftypedefrecord生成的类时,您可以跳过类型列表,因为这些类总是只有两个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)

答案 1 :(得分:4)

由于'new'是一种特殊形式,我不确定如果没有宏,你可以做到这一点。以下是使用宏的方法:

user=> (defmacro str-new [s & args] `(new ~(symbol s) ~@args))
#'user/str-new
user=> (str-new "String" "LOL")
"LOL"

查看Michal对此宏限制的评论。

答案 2 :(得分:3)

这是一种扩展defrecord以自动创建命名良好的构造函数以构造记录实例(新的或基于现有记录)的技术。

http://david-mcneil.com/post/765563763/enhanced-clojure-records

答案 3 :(得分:3)

在Clojure 1.3中,defrecord将使用记录名称“ - >”自动定义工厂功能前缀。同样,采用地图的变体将是“map->”前面的记录名称。

user=> (defrecord MyRec [a b])
user.MyRec
user=> (->MyRec 1 "one")
#user.MyRec{:a 1, :b "one"}
user=> (map->MyRec {:a 2})
#user.MyRec{:a 2, :b nil}

像这样的宏应该可以从记录类型的字符串名称创建一个实例:

(defmacro newbie [recname & args] `(~(symbol (str "->" recname)) ~@args))