问题
I'm now making a class object with Clojure which has a method returning the object itself.
Written with Java, the object that I'd like to make is like,
class Point {
public double x;
public double y;
public Point(double x, double y) {
this.x = x;
this.y = y;
}
public Point copy() {
return new Point(this.x, this.y);
}
}
The current clojure code that I wrote is like,
(ns myclass.Point
:gen-class
:prefix "point-"
:init init
:state state
:constructors {[double double] []}
:methods [[copy [] myclass.Point]]))
(defn point-init [x y]
[[] {:x x :y y}])
(defn point-copy [this]
this)
However, I got an error as follows.
java.lang.ClassNotFoundException: myclass.Point
While I have googled about this issue, I couldn't find any answers. Does anyone know the solution for this issue?
Thank you in advance for your help.
回答1:
I'm not sure it's the only way, but, in order to use the generated class type in a method signature, you can generate the class in 2 steps - though it's still in one file and compiled in one pass:
- call
gen-class
with only the constructors - call
gen-class
again with state, full set of constructors and methods.
I tried with various methods including forward declaration, but only the above was working eventually. I did extend your example a little bit. Note That the copy
method is not very useful as-is since Point
is immutable, but you may want to provide mutators to your class.
Full listing:
(ns points.Point)
;; generate a simple class with the constructors used in the copy method
(gen-class
:name points.Point
:init init
:constructors {[] []
[double double] []})
;; generate the full class
(gen-class
:name points.Point
:prefix pt-
:main true
:state coordinates
:init init
:constructors {[] []
[double double] []}
:methods [[distance [points.Point] double]
[copy [] points.Point]])
(defn pt-init
([] (pt-init 0 0))
([x y]
[[] {:x x :y y}]))
(defn pt-copy
"Return a copy of this point"
[this]
(points.Point. (:x (.coordinates this)) (:y (.coordinates this))))
(defn pt-distance [^points.Point this ^points.Point p]
(let [dx (- (:x (.coordinates this)) (:x (.coordinates p)))
dy (- (:y (.coordinates this)) (:y (.coordinates p)))]
(Math/sqrt (+ (* dx dx) (* dy dy)))))
(defn pt-toString [this]
(str "Point: " (.coordinates this)))
;; Testing Java constructors and method call on Point class
(import (points Point))
(defn pt-main []
(let [o (Point.)
p (points.Point. 3 4)]
(println (.toString o))
(println (.toString p))
(println (.distance o p))
(println (.distance p (.copy p)))))
In order to generate the classes, configure project.clj
with the line
:aot [points.Point]
Testing with lein
gives:
tgo$ lein clean
tgo$ lein compile
Compiling points.Point
tgo$ lein run
Point: {:x 0, :y 0}
Point: {:x 3.0, :y 4.0}
5.0
0.0
回答2:
Cause analysis
The issue is because the compiler doesn't know about myclass.Point
in the :methods
directive before the class myclass.Point
is actually generated. Although this isn't an issue to a Java class but Clojure compiler doesn't seem to support this use case, (perhaps for good reasons.)
Solution
I feel it's much easier to implement interfaces (in the :implements
directive) other than defining custom methods like your example. This might also suggest the good practice of "programming to interfaces." Generating interface from Clojure is doable, e.g. gen-interface. Just make sure the gen-interface
is compiled aot, before the gen-class
form.
Alternatively, I prefer to create a polyglot project with interfaces in Java and implementations in Clojure. Here's the code snippet to do so with leiningen:
// src/java/points/Point.java
package points;
public interface Point {
public double distant(Point p);
public Point copy();
}
;; project.clj
(defproject
;; Change these. The rests are the same.
:aot [points.java-class]
:source-paths ["src/clojure"]
:java-source-paths ["src/java"])
;; src/clojure/points/java_class.clj
(ns points.java-class
(:gen-class
:name points.Point2D
:implements [points.Point] ;; no more ClassNotFoundException
:prefix "point-"
:init init
:state state
:constructors {[double double] []})
;; rests are the same
This works because Leiningen compiles the java sources first by default.
This answer is also covered in my article, although I modified the code snippet to fit your question.
来源:https://stackoverflow.com/questions/29329798/clojure-gen-class-returning-own-class