3

假设有一个 Java 类没有为它的所有字段提供 getter 和 setter,我必须用它们来扩展:gen-class和摆弄它们。

如何访问超类字段?

我现在想到的最快(也许是最干净的......)解决方案是创建一个扩展我的超类的 java 类,并改为扩展它,但我想知道是否有一个听起来更直接的替代方案。

谢谢!

4

2 回答 2

4

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

这种方法适用于公共和受保护领域。它不适用于私有字段。

假设 Java 基类是这样的:

package fields;

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

生成子类的 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
于 2017-06-29T18:46:57.187 回答
1

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

> 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类:

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“包保护”(无限定符),则失败。这是有道理的,因为我们的子类在不同的包中。

此外,如果我给子类指定一个不同于默认值的名称,即 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 中,子类通常不能看到超类的私有成员变量;来自不同包的“包保护”成员也受到限制。这是讨厌的 Java 类:

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

但是,Java 具有众所周知的覆盖private访问限制的能力,您甚至不需要子类!您需要做的就是使用反射。这是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

在此处查看 AccessibleObject 的文档。请注意,反射期间返回的类Field&Method都包括在内。

于 2017-06-29T14:41:39.253 回答