我知道我可以在Clojure:中实例化这样的Java类
(new Classname args*)
假设我收到了构造函数使用的参数的列表。那么我该如何实例化类?我不能使用apply
,因为new
不是函数。
有两种基本方法:
-
反射:
(clojure.lang.Reflector/invokeConstructor Klass (to-array [arg ...]))
缓慢但充满活力。
-
事先打开自变量的包装:
(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
的名称创建了一个位置工厂函数。