Clojure:从 String 类名创建新实例 [英] Clojure: creating new instance from String class name

查看:24
本文介绍了Clojure:从 String 类名创建新实例的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在 Clojure 中,给定一个字符串形式的类名,我需要创建该类的一个新实例.换句话说,我将如何在

中实现 new-instance-from-class-name

(def my-class-name "org.myorg.pkg.Foo");使用参数 1、2 和 3 调用 org.myorg.pkg.Foo 的构造函数(new-instance-from-class-name my-class-name 1 2 3)

我正在寻找比

更优雅的解决方案
  • 在类的构造函数上调用 Java newInstance 方法
  • 使用 eval、加载字符串、...

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

解决方案

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

首先是反射:

<前>(clojure.lang.Reflector/invokeConstructor(解析(符号整数"))(到数组 ["16"]))

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

第二个选项尽可能快,但稍微复杂一点,并且使用eval:

<前>(defn make-factory [类名和类型](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))(工厂内42")

关键点是评估定义匿名函数的代码,就像 make-factory 所做的那样.这 -- 比上面的反射示例慢,所以尽可能少做,例如每个类一次.但是这样做之后,您就有了一个常规的 Clojure 函数,您可以将其存储在某个地方,例如本示例中的 int-factory 之类的 var,或者存储在哈希映射或向量中,具体取决于您将如何使用它.无论如何,这个工厂函数将以完全编译的速度运行,可以被 HotSpot 内联,并且总是比反射示例运行.

当您专门处理由 deftypedefrecord 生成的类时,您可以跳过类型列表,因为这些类总是恰好有两个具有不同元数的构造函数.这允许类似:

<前>(defn 记录工厂 [记录名称](let [recordclass ^Class (resolve (symbol recordname))max-arg-count (apply max (map #(count (.getParameterTypes %))(.getConstructors 记录类)))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"))(示例记录工厂F"斯科特"菲茨杰拉德")

In Clojure, given a class name as a string, I need to create a new instance of the class. In other words, how would I implement new-instance-from-class-name in

(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) 

I am looking for a solution more elegant than

  • calling the Java newInstance method on a constructor from the class
  • using eval, load-string, ...

In practice, I will be using it on classes created using defrecord. So if there is any special syntax for that scenario, I would be quite interested.

解决方案

There are two good ways to do this. Which is best depends on the specific circumstance.

The first is reflection:

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

That's like calling (new Integer "16") ...include any other ctor arguments you need in the to-array vector. This is easy, but slower at runtime than using new with sufficient type hints.

The second option is as fast as possible, but a bit more complicated, and uses 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")

The key point is to eval code that defines an anonymous function, as make-factory does. This is slow -- slower than the reflection example above, so only do it as infrequently as possible such as once per class. But having done that you have a regular Clojure function that you can store somewhere, in a var like int-factory in this example, or in a hash-map or vector depending on how you'll be using it. Regardless, this factory function will run at full compiled speed, can be inlined by HotSpot, etc. and will always run much faster than the reflection example.

When you're specifically dealing with classes generated by deftype or defrecord, you can skip the type list since those classes always have exactly two ctors each with different arities. This allows something like:

(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)

这篇关于Clojure:从 String 类名创建新实例的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆