在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)
我正在寻找比
更优雅的解决方案在实践中,我将在使用defrecord创建的类上使用它。因此,如果该场景有任何特殊语法,我会非常感兴趣。
答案 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等内联运行,并且总是比反射示例运行更快。
当您专门处理由deftype
或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)
答案 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))