I was having a bit of trouble understanding all of the details and decided to try out a minimal version. Here is a file listing:
> 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
Here is the 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" ]
)
and the Java class:
package jpak;
public class Base {
public long answer = 41;
}
and our Clojure code:
(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)
))
We can run using lein run
to get:
> lein run
old answer = 41
new answer = 42
Version 2
The above continues to work if the Java variable answer
is protected, but fails if it is either private
or "package protected" (no qualifier). This makes sense since our subclass is in a different package.
Also, it is a little cleaner if I give the subclass a name different than the default value, which is the clojure namespace name "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)
))
Version 3: Access private
member values
In Java, a subclass cannot normally see private member variables of a superclass; "package protected" members from a different package are also restricted. Here is the pesky Java class:
package jpak;
public class Base {
private long answer = 41;
}
However, Java has a well-known ability to override private
access restrictions, and you don't even need a subclass! All you need to do is use reflection. Here is the clojure version:
(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. Note that Field
& Method
, which are the classes returned during reflection, are both included.