clojure - 如何(以及何时)调用点运算符?

时间:2015-06-02 13:08:52

标签: java clojure quote

更新

这个问题在很大程度上是重复的,但下面的问题和我的答案似乎都更具说明性。有关上一个问题,请参阅Access Java fields dynamically in Clojure?

我正在尝试从Clojure中查找Java对象实例中的字段设置。点运算符似乎在我天真地期望它工作的地方失败。

例如,有了这些定义......

(defn example-point []
  (let [instance (java.awt.Point. 1 2)]
     (list (. instance x)  (. instance y))))

(defn example-point-1 []
  (let [instance (java.awt.Point. 1 2)
        fields '("x" "y")]
    (map #(. instance (symbol %))
         fields)))

(defn example-point-2 []
  (let [instance (java.awt.Point. 1 2)
        fields (map symbol '("x" "y"))]
    (map (fn [field] (eval `(. ~instance ~field)))
         fields)))

我得到这些返回值:

flood.core> (example-point)

  

(1 2)

那很棒,但是如果我想提供字段的名称"以编程方式&#34 ;?这是其他功能应该做的事情。对于我天真的思维方式,他们应该返回与上面相同的价值。但他们都有不同的错误。

flood.core> (example-point-1)

  

IllegalArgumentException找不到匹配的方法:类java.awt.Point的符号clojure.lang.Reflector.invokeMatchingMethod(Reflector.java:53)

flood.core> (example-point-2)

  

CompilerException java.lang.RuntimeException:无法在代码中嵌入对象,也可能没有定义print-dup:java.awt.Point [x = 1,y = 2],编译:(/ tmp / form- init1213427540543573506.clj:1:5659)

我被困了,你能帮我解决这个问题吗?

进一步更新

我确实尝试了这个,它又产生了另一个错误。

;; added in response to suggestion...
(defn example-point-3 []
  (let [instance (java.awt.Point. 1 2)
        fields (map symbol '("x" "y"))]
    (map (fn [field] (eval `(. instance ~field)))
         fields)))

flood.core> (example-point-3)

  

CompilerException java.lang.RuntimeException:没有这样的var:flood.core / instance,编译:(/ tmp / form-init1213427540543573506.clj:1:5659)

2 个答案:

答案 0 :(得分:3)

以下代码示例以自包含的方式再现了Joost Diepenmaat在Access Java fields dynamically in Clojure?的回答中的主要观点。实际使用时,他的java-fields包也在Clojars以及on Github,并提供了一个fields函数,可以应用于Java类的实例。

(import java.lang.reflect.Field)
(import java.lang.reflect.Modifier)

(defn example-point* [] 
  (let [fields (filter #(let [m (.getModifiers %)]
                          (and (not (= 0 (bit-and Modifier/PUBLIC m)))
                               (= 0 (bit-and Modifier/STATIC m))))
                       (.getDeclaredFields java.awt.Point))
        instance (java.awt.Point. 1 2)]
    (map #(.get % instance) fields)))

即使对于包含一个静态私有字段的Points,也需要进行此过滤,如评估(map #(identity %) (.getDeclaredFields java.awt.Point))所示。

但是,如果我们从公共字段的已知名称开始,那么以下较短的答案可行,并且稍微接近原始问题的范围。这需要java.lang.reflect.Field

(defn example-point** [] 
  (let [field-names '("x" "y")
        instance (java.awt.Point. 1 2)]
    (map #(.get (.getField java.awt.Point %) instance) field-names)))

请注意,此处根本不使用点运算符。

答案 1 :(得分:2)

第一个错误是clojure评估following form

的结果
(. instance-expr (method-symbol args*))

如果您将列表作为.特殊表单的第二个参数传递,则将其解释为对参数method-symbol的方法args的调用(即(method-symbol args*)为未评估)。

在第二种情况下,你没有引用instance不必要的。由于clojure会评估instance符号并在语法引用的表单中替换它的值,因此您的案例中的结果如下:

(. <instance of point> x)

然后传递给eval。 Eval期望clojure代码进行评估,如果实例不正确(.必须有一个符号作为第一个参数)。

一种解决方案是在instance 中取消引用example-point-2