我有几天学习Clojure并且有一些出牙问题,所以我在寻求建议。
我正在尝试将一个Java类存储在Clojure var中并调用其静态方法,但它不起作用。
示例:
user=> (. java.lang.reflect.Modifier isPrivate 1)
false
user=> (def jmod java.lang.reflect.Modifier)
#'user/jmod
user=> (. jmod isPrivate 1)
java.lang.IllegalArgumentException: No matching method found: isPrivate for class java.lang.Class (NO_SOURCE_FILE:0)
at clojure.lang.Compiler.eval(Compiler.java:4543)
从异常看起来,运行时期望var保存一个对象,因此它调用.getClass()来获取类并使用反射查找方法。在这种情况下,var已经拥有一个类,因此.getClass()
返回java.lang.Class
,方法查找显然失败。
除了编写自己的宏之外,还有什么方法吗?
在一般情况下,我想在varible中拥有一个对象或一个类,并在其上调用适当的方法 - 为静态方法和实例方法打字。
在这个特定的情况下,我只想更新java.lang.reflect.Modifier
的名称,如果你愿意,可以使用别名。我知道import
,但是寻找更通用的东西,比如Clojure名称空间别名,但是对于Java类。还有其他机制吗?
修改
也许我只是对这里的调用约定感到困惑。我认为Lisp(以及扩展的Clojure)模型是评估所有参数并将列表中的第一个元素称为函数。
在这种情况下,(= jmod java.lang.reflect.Modifier)
返回true,(.getName jmod)
和(.getName java.lang.reflect.Modifier)
都返回相同的字符串。
因此变量和类名明确地评估为相同的东西,但它们仍然不能以相同的方式被调用。这是怎么回事?
修改2
回答我的第二个问题(这里发生了什么),Clojure的医生说
“The Dot special form”下的如果第一个操作数是符号 解析为类名,访问权限 被认为是静态成员 命名类的......否则就是 被认为是实例成员
http://clojure.org/java_interop
“解析为类名”显然与“评估解析为类名的东西”不同,所以我在这里尝试做的不是点特殊形式支持。
答案 0 :(得分:6)
(更新:我已经准备了一些可以接受的解决方案......原来的答案仍然低于帖子末尾的水平规则。)
我刚刚写了一个宏来启用它:
(adapter-ns java.lang.reflect.Modifier jmod)
; => nil
(jmod/isStatic 1)
; => false
(jmod/isStatic 8)
; => true
我们的想法是创建一个单一用途的命名空间,将给定类的静态作为Vars导入该命名空间,然后将命名空间别名为一些有用的名称。令人费解,但它的确有效! : - )
代码如下所示:
(defmacro import-all-statics
"code stolen from clojure.contrib.import-static/import-static"
[c]
(let [the-class (. Class forName (str c))
static? (fn [x]
(. java.lang.reflect.Modifier
(isStatic (. x (getModifiers)))))
statics (fn [array]
(set (map (memfn getName)
(filter static? array))))
all-fields (statics (. the-class (getFields)))
all-methods (statics (. the-class (getMethods)))
import-field (fn [name]
(list 'def (symbol name)
(list '. c (symbol name))))
import-method (fn [name]
(list 'defmacro (symbol name)
'[& args]
(list 'list ''. (list 'quote c)
(list 'apply 'list
(list 'quote (symbol name))
'args))))]
`(do ~@(map import-field all-fields)
~@(map import-method all-methods))))
(defmacro adapter-ns [c n]
(let [ias (symbol (-> (resolve 'import-all-statics) .ns .name name)
"import-all-statics")]
`(let [ns-sym# (gensym (str "adapter_" ~n))]
(create-ns 'ns-sym#)
(with-ns 'ns-sym#
(clojure.core/refer-clojure)
(~ias ~c))
(alias '~n 'ns-sym#))))
上面查找Var以一种有点复杂的方式保存import-all-statics
宏(但是,如果宏从当前命名空间可见,则可以保证工作)。如果你知道它将找到哪个命名空间,我写的原始版本更简单:
(defmacro adapter-ns [c n]
`(let [ns-sym# (gensym (str "adapter_" ~n))]
(create-ns 'ns-sym#)
(with-ns 'ns-sym#
(clojure.core/refer-clojure)
;; NB. the "user" namespace is mentioned below;
;; change as appropriate
(user/import-all-statics ~c))
(alias '~n 'ns-sym#)))
(下面的原始答案。)
我意识到这不是你要求的,但也许clojure.contrib.import-static/import-static
对你有用:
(use 'clojure.contrib.import-static)
(import-static clojure.lang.reflect.Modifier isPrivate)
(isPrivate 1)
; => false
(isPrivate 2)
; => true
请注意import-static
将静态方法导入为宏。
答案 1 :(得分:1)
您已成功将该类存储在jmod中,但isPrivate是java.lang.reflect.Modifier的静态方法,而不是java.lang.Class。
你可以用反射来做到这一点:
(. (. jmod getMethod "isPrivate" (into-array [Integer/TYPE]))
invoke nil (into-array [1]))
答案 2 :(得分:0)
这是一个受前两个答案启发的宏,它使用类名和对象上的实例方法处理类名和变量的静态方法:
(defmacro jcall [obj & args]
(let [ref (if (and (symbol? obj)
(instance? Class (eval obj)))
(eval obj)
obj) ]
`(. ~ref ~@args)))
作为宏的相对新手,棘手的部分是让评估顺序正确。
对于其他新手:宏的obj参数在没有评估的情况下传入,我们需要强制对vars进行评估,以便var名称扩展为它所拥有的类名。在实际的宏体之外,我们需要一个显式的eval。
是否obj是符号的测试是将评估限制为变量。变量是否包含类的测试是跳过非类的评估,然后它也适用于对象和实例方法。
使用示例:
;; explicit class name, static method
user=> (jcall java.lang.reflect.Modifier isPrivate 1)
false
;; class name from var, static method
user=> (jcall jmod isPrivate 1)
false
;; works for objects and instance methods too
user=> (jcall (Object.) toString)
"java.lang.Object@3acca07b"
;; even with the object in a variable
user=> (def myobj (Object.))
#'user/myobj
user=> (jcall myobj toString)
"java.lang.Object@4ccbb612"
;; but not for instance methods on classes
user=> (jcall Object toString)
java.lang.NoSuchFieldException: toString (NO_SOURCE_FILE:747)