在Clojure中,如何使用存储在变量中的java类?
我该如何修复以下代码?
(def a java.lang.String)
(new a "1"); CompilerException java.lang.IllegalArgumentException: Unable to resolve classname: a
为什么这个工作正常?
(def a str)
(a "1")
答案 0 :(得分:8)
最优雅的解决方案是编写与construct
相同但能够动态接收类的new
:
(defn construct [klass & args]
(clojure.lang.Reflector/invokeConstructor klass (into-array Object args)))
(def a HashSet)
(construct HashSet '(1 2 3)); It works!!!
此解决方案克服了@mikera答案的限制(见评论)。
特别感谢@Michał Marczyk让我意识到invokeConstructor
回答了我的另一个问题:Clojure: how to create a record inside a function?。
另一种选择是将对构造函数的调用存储为匿名函数。在我们的案例中:
(def a #(String. %1))
(a "111"); "111"
答案 1 :(得分:7)
以这种方式定义a时,会得到一个包含java.lang.Class
的var(def a java.lang.String)
(type a)
=> java.lang.Class
然后你有两个选择:
答:通过使用反射API查找Java构造函数来动态构造新实例。请注意,正如Yehonathan指出的那样,您需要使用构造函数签名中定义的 exact 类(子类不起作用,因为它找不到正确的签名):
(defn construct [klass & args]
(.newInstance
(.getConstructor klass (into-array java.lang.Class (map type args)))
(object-array args)))
(construct a "Foobar!")
=> "Foobar!"
B:使用Clojure的Java互操作构建,这需要一个eval:
(defn new-class [klass & args]
(eval `(new ~klass ~@args)))
(new-class a "Hello!")
=> "Hello!"
请注意,方法A相当快(在我的机器上快了大约60倍),我认为这主要是因为它避免了为每个eval语句调用Clojure编译器的开销。
答案 2 :(得分:6)
问题在于Clojure使用许多特殊形式实现Java互操作:
user=> (doc new)
-------------------------
new
Special Form
Please see http://clojure.org/special_forms#new
nil
这基本上意味着"正常"修改Clojure语法以在调用Java时允许更加方便的构造。作为动态Java需求的天真反射解决方案,您可以利用eval
:
user=> (def a String) ; java.lang package is implicitly imported
#'user/a
user=> `(new ~a "test") ; syntax quote to create the correct form
(new java.lang.String "test")
user=> (eval `(new ~a "test")) ; eval to execute
"test"
同样的策略适用于所有其他互操作特殊表单,例如method invocation。