【问题标题】:Clojure: creating new instance from String class nameClojure:从字符串类名创建新实例
【发布时间】:2011-04-14 11:54:19
【问题描述】:

在 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、加载字符串、...

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

【问题讨论】:

    标签: constructor clojure


    【解决方案1】:

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

    【讨论】:

      【解决方案2】:

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

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

      【讨论】:

        【解决方案3】:

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

        首先是反射:

        (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 所做的那样。这是 slow - 比上面的反射示例慢,所以尽可能不频繁地这样做,例如每个班级一次。但是完成之后,您就有了一个常规的 Clojure 函数,您可以将它存储在某个地方,在这个例子中像 int-factory 这样的 var 中,或者在一个哈希映射或向量中,这取决于您将如何使用它。无论如何,这个工厂函数将以完整的编译速度运行,可以被 HotSpot 内联等,并且总是比反射示例更快运行。

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

        (定义记录工厂 [记录名称] (let [recordclass ^Class (resolve (symbol recordname)) max-arg-count (应用 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")) (example-record-factory "F." "Scott" 'Fitzgerald)

        【讨论】:

        • 太棒了!第二种选择显然是一种非常通用的技术。我已经以另一种方式使用它了。
        【解决方案4】:

        由于“新”是一种特殊形式,我不确定您是否可以在没有宏的情况下执行此操作。这是一种使用宏的方法:

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

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

        【讨论】:

        • 请注意,这仅在宏接收到的s 是(文字)字符串而不是对字符串求值的任意表达式时才有效。在后一种情况下,无法避免 eval 或反射实例构造。
        • 恐怕 s 不会是文字字符串。我已编辑问题以反映这一点。
        猜你喜欢
        • 1970-01-01
        • 2020-07-30
        • 2016-07-25
        • 2015-07-22
        • 2014-05-31
        • 2011-04-09
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多