用 :gen-class 扩展一个 class 暴露它的裸字段

Extending with :gen-class a class that exposes its naked fields

假设有一个 Java class 没有为其所有字段提供 getter 和 setter,我必须用 :gen-class 和 fiddle 扩展它和他们在一起。

如何访问超级class 字段?

我现在想到的最快(也许是最干净...)的解决方案是创建一个 java class 来扩展我的超级 class,然后扩展它相反,但我想知道是否有其他听起来更直接的选择。

谢谢!

我在理解所有细节时遇到了一些困难,因此决定尝试一个最小版本。这是一个文件清单:

> d **/*.{clj,java}
-rw-rw-r-- 1 alan alan 501 Jun 29 17:11 project.clj
-rw-rw-r-- 1 alan alan 431 Jun 29 17:10 src/demo/core.clj
-rw-rw-r-- 1 alan alan  63 Jun 29 16:57 src-java/jpak/Base.java

这是project.clj

(defproject demo "0.1.0-SNAPSHOT"
  :description "demo code"
  :license {:name "Eclipse Public License"
            :url "http://www.eclipse.org/legal/epl-v10.html"}
  :dependencies [
    [org.clojure/clojure "1.8.0"]
    [tupelo "0.9.55"]
  ]
  :profiles {:dev {:dependencies [ [org.clojure/test.check "0.9.0"] ] }
             :uberjar {:aot :all} }
  :java-source-paths ["src-java"]
  :aot [ demo.core ]
  :main ^:skip-aot demo.core
  :target-path "target/%s"
  jvm-opts ["-Xms500m" "-Xmx500m" ]
)

和 Java class:

package jpak;
public class Base {
  public long answer = 41;
}

和我们的 Clojure 代码:

(ns demo.core
  (:gen-class
   :extends jpak.Base
   :exposes {answer {:get getans :set setans}}
 ))

(defn -main
  [& args]
  (let [sub-obj    (demo.core.) ; default name of subclass
        old-answer (.getans sub-obj)
        >>         (.setans sub-obj (inc old-answer))
        new-answer (.getans sub-obj)  ]
    (println "old answer = " old-answer)
    (println "new answer = " new-answer)
  ))

我们可以运行使用lein run得到:

> lein run  
old answer =  41
new answer =  42

版本 2

如果 Java 变量 answer 受到保护,上述方法继续工作,但如果它是 private 或 "package protected"(无限定符)则失败。这是有道理的,因为我们的 subclass 在不同的包中。

此外,如果我给 subclass 一个不同于默认值的名称,即 clojure 名称空间名称 "demo.core":

(ns demo.core
  (:gen-class
   :name demo.Sub
   :extends jpak.Base
   :exposes {answer {:get getans :set setans}}
 ))

(defn -main
  [& args]
  (let [sub-obj    (demo.Sub.) ; new name of subclass
        old-answer (.getans sub-obj)
        >>         (.setans sub-obj (inc old-answer))
        new-answer (.getans sub-obj)
  ]
    (println "old answer = " old-answer)
    (println "new answer = " new-answer)
  ))

版本 3:访问​​ private 成员值

在Java中,subclass通常看不到superclass的private成员变量; "package protected" 来自不同包的成员也受到限制。这是讨厌的 Java class:

package jpak;
public class Base {
  private long answer = 41;
}

然而,Java 具有超越 private 访问限制的众所周知的能力,您甚至不需要子class!您需要做的就是使用反射。这是 clojure 版本:

(ns demo.break
  (:import [jpak Base]))

(defn -main
  [& args]
  (let [base-obj   (Base.)
        class-obj  (.getClass base-obj)
        ans-field  (.getDeclaredField class-obj "answer")
        >>         (.setAccessible ans-field true)
        old-answer (.get ans-field base-obj)
        >>         (.set ans-field base-obj 42)
        new-answer (.get ans-field base-obj)
  ]
    (println "old answer = " old-answer)
    (println "new answer = " new-answer)))

> lein run -m demo.break
old answer =  41
new answer =  42

See the docs for AccessibleObject here。请注意 FieldMethod,它们是反射期间返回的 classes,都包括在内。

生成的 classes 中的方法可以在 gen-class:exposes 选项的帮助下访问基础 class 字段。 :exposes 需要一个映射,其中键是与基础 class 字段名称匹配的符号;值也是像 {:get getterName, :set setterName} 这样的映射。 Clojure 自动生成那些 getter 和 setter 方法。它们可用于读取和修改基础 class 字段。 gen-classdocstring 中记录了这一点。

此方法适用于 public 和受保护的字段。它不适用于私有字段。

假设 Java 基础 class 是这样的:

package fields;

class Base {
    public String baseField = "base";
}

生成子 class 的 Clojure 代码为:

(ns fields.core
  (:gen-class
   :extends fields.Base
   :methods [[bar [] String]
             [baz [String] Object]]
   :exposes { baseField { :get getField :set setField }}))

(defn -bar [this]
  (str (.getField this) "-sub"))

(defn -baz [this val]
  (.setField this val)
  this)

(defn -main
  [& args]
  (println (.. (fields.core.) (bar)))
  (println (.. (fields.core.) (baz "new-base") (bar))))

假设所有这些都是 AOT 编译的并且运行,输出是:

base-sub
new-base-sub