First, I'm assuming that we'll be creating Clojure objects from Java, calling methods on those Clojure objects from bog-standard Java code. This, I think, is the most likely scenario for the budding Clojure hacker who wants to write part of an existing Java system in Clojure.
Environment
First, you'll need to have obtained your
clojure.jar
, either from the snapshots here, or from the Subversion repository and building yourself.Next, in whatever environment you're using, add the full path to
clojure.jar
to your CLASSPATH
. Also add ".
" and "classes
" (both relative paths, not absolute). You'll see why later.The Clojure Code
In the directory you're building from, create the directory structure for your package, Java-style. I'm going to be putting my Clojure code in
org/djw/sample.clj
, so org/djw
will have to exist beforehand.In
sample.clj
, most of the magic's in the namespace declaration.
(ns org.djw.sample ;; 1
(:import (javax.swing JFrame)) ;; 2
(:gen-class ;; 3
:name org.djw.DJW ;; 4
:extends javax.swing.JFrame ;; 5
:constructors {[String] [String]} ;; 6
:init initialise ;; 7
:implements [Runnable] ;; 8
:state fiddlyBits)) ;; 9
- 1. This is your Clojure namespace. It doesn't mean much from Java-land.
- 2. You can import any classes used by your Clojure file here.
- 3.
:gen-class
allows the compiler to generate Java bytecode files. - 4. This is the fully-qualified name of the Java class you want to emit. The Clojure
compile
directive generates quite a few class files that your Java code doesn't need to know about: the one in:name
is an exception: it's what your Java code willimport
- 5. If your class subclasses something other than
Object
, name it here, as normal - 6. I want to have a
String
constructor that calls theString
constructor inJFrame
. - 7. The initialiser function for new instances. I lack imagination, so have called it
initialise
here. - 8. Horribly, my new class is both a GUI element (a
JFrame
) and aRunnable
. Since you can implement many interfaces, this appears in a literal vector. - 9. The
initialise
function gets to attach some Clojure-side state to the object being created (you'll see that in a bit). The:state
specifier creates a final instance method to access that state from Java.
This leaves two functions to implement in the Clojure code:
initialise
and run
(the latter required by Runnable
).
(defn -initialise [message]
[[message] (ref {:message message})])
(defn -run [instance]
(let [message (:message @(.fiddlyBits instance))]
(println message)))
Note the dash in front of the function names (this is the default
:prefix
from :gen-class
). Also, note that the function specified by :init
in :gen-class
didn't get an instance to play with, whereas run
(an instance method), does. This instance is the object that the method was invoked against, effectively this
from Java.initialise
has to return a vector of two elements: the first consists of the arguments to pass to the superclass constructor. The second is the state that should be attached on a per-instance basis.run
is a Runnable.run
implementation, and just prints out the message that the instance was created with. Note that the state is accessed with (.fiddlyBits instance)
, returning the ref
-wrapped Clojure map, dereferenced with '@' as normal and with the :message
key used to look up the associated value.Compiling the Clojure Code to .class
Files
Create a directory called
classes/[package-name]
, in my case classes/org/djw
. Why classes
? Well, that's what Clojure's global *compile-path*
variable is by default, so is the root where the compile
command emits .class
files. That's why you added it to your CLASSPATH
above (you did do that, didn't you..?)Since
clojure.jar
is on your CLASSPATH
, you can start a Clojure REPL with just 'java clojure.main
'. You can now compile the ./org/djw/sample.clj
like this:
danny@mirror Desktop [10] % java clojure.main
Clojure 1.1.0-alpha-SNAPSHOT
user=> (compile 'org.djw.sample)
org.djw.sample
user=>
And that's it.
classes/org/djw
will now contain org.djw.DJW
(as per gen-class
's :name
field). It'll also contain a bunch of other .class
files: don't delete these, they are required! Clojure creates a .class
file per function (including unnamed functions), as well as another for initialisation.The Java Code
This has been a lot of effort so far. But the good news that this allows Java to talk to Clojure-generated class files without having any idea that Clojure was the source language. The Java code in
Test.java
to use it might look like this:
import org.djw.DJW;
public class Test
{
public static void main(String [] args)
{
DJW djw = new DJW("Hello");
new Thread(djw).start();
}
}
And that's it.
DJW
is the class name, and a new instance is obtained just as with any other class. It faithfully implements Runnable
, as specified in the Clojure code, and it can be duly run from a Thread
created from Java.Compiling and Running the Java Code
The Java can be compiled just as with any other. Remember that the
classes
folder must be in your CLASSPATH
for javac
to see the definition of DJW
.With
clojure.jar
, '.'
and classes
in your CLASSPATH
, you can just run as you'd expect:
danny@mirror Desktop [30] % javac Test.java
danny@mirror Desktop [31] % java Test
Hello
danny@mirror Desktop [32] %
By now, you've defined a class definition in Clojure, prompted instance creation from Java, initialised the object in Clojure, handed it to a thread in Java, and printed out a message in Clojure. That's a fair amount of bouncing around, especially for such a trivial example, but hopefully you've found it useful for what you need.
This was very helpful! Thanks =]
ReplyDeleteThanks,this makes some things clearer.
ReplyDeleteBut how i can import this class with Clojure's code? Does :import works same as java's import?
Concrete case:
altra/lf.clj file:
(ns altra.lf
(:gen-class
:prefix lf-
:name altra.lf.LF))
altra/main.clj file:
(mycompile 'altra.lf) ; it working properly, be sure
(ns altra.main
(:import
(altra.lf LF)))
(def qwe (LF.))
Everything looks fine, classes files created, but it don't founds class LF (in `qwe' definition). I tried many different calls (require, use and other, in many forms) -- class not found, namespace not found, etc.
Can you show similar example? I'll be so thankful))
@Igor: As far as Clojure is concerned, ahead-of-time generated classes are just the same as any other Java class. Is the classes directory where the .class files were generated in your CLASSPATH? I just tried your code in a fresh session, and it compiles just fine.
ReplyDeleteIn the top level directory (from which all commands were executed), I had the 'altra' and 'classes' directories, and my CLASSPATH at the time was:
/Users/danny/SandBox/clojure/clojure-1.2.0.jar:.:classes
on Mac OS 10.6.
Hope that helps.
@Danny: Thank you.
ReplyDeleteI chose intellij idea for work, but never learned how to build project. The problem was in my build instruments.
You said that the code is correct and it helped. Thank you!)
Thanks! That really helps.
ReplyDelete