使用构造函数参数列表而不是参数本身调用Java new(在Clojure中)



我知道我可以在Clojure:中实例化这样的Java类

(new Classname args*)

假设我收到了构造函数使用的参数的列表。那么我该如何实例化类?我不能使用apply,因为new不是函数。

有两种基本方法:

  1. 反射:

    (clojure.lang.Reflector/invokeConstructor Klass (to-array [arg ...]))
    

    缓慢但充满活力。

  2. 事先打开自变量的包装:

    (let [[arg1 arg2 ...] args]
      (Klass. arg1 arg2 ...))
    

    (Klass. ...)(new Klass ...)的惯用书写方式;它在宏扩展时转换为后一种形式。)

    如果编译器能够推断出将使用哪个构造函数,则速度会更快(您可能需要提供适当的类型提示——使用(set! *warn-on-reflection* true)来查看是否正确)。

当然,第二种方法有点笨拙。如果您希望在代码的许多地方构造大量的Klass实例,那么您可以编写一个适当的工厂函数。如果你希望用这种方式处理许多类,你可以抽象出定义工厂函数的过程:

(defmacro deffactory [fname klass arg-types]
  (let [params (map (fn [t]
                      (with-meta (gensym) {:tag t}))
                    arg-types)]
    `(defn ~(with-meta fname {:tag klass}) ~(vec params)
       (new ~klass ~@params))))

最后,如果定义工厂函数的过程本身需要完全动态的,你可以做一些类似Chouser解决这个问题的第二种方法:定义一个函数而不是宏,并使其eval类似于上面引用的语法(defn ...)形式(引用的语法=前面有backtick;我不知道如何在SO帖子中包含字面上的backtick),除了您想要使用fn而不是defn,并且可能跳过fname。对编译器的调用将是昂贵的,但返回的函数将像任何Clojure函数一样执行;请参阅Chouser的上述回答,了解稍长的讨论。

为了完整起见,如果您使用的是Clojure 1.3或更高版本,并且所涉及的Java类实际上是一个Clojure记录,那么已经以->RecordName的名称创建了一个位置工厂函数。

最新更新