Generating classes in Clojure
Posted on 2025-03-01 Edit on GitHub
I rarely use the :gen-class facility in Clojure, but when I do, I usually trip over one issue or another. After finally spending some time to read the documentation and peruse source code, I've got a better understanding of how it all fits together.
Write that down, write that down!
When Clojure code is evaluated, the compiler generates JVM bytecode and loads it into memory. For various reasons, such as improved startup time, it can be advantageous to compile namespaces into actual classfiles. These compiled namespaces need to be :import
'ed instead of :require
'ed.
A namespace is normally compiled into a class ending in __init
. For the simplest namespace:
(ns clj-genclass.core)
We can compile with Leiningen:
lein compile clj-genclass.core
And see the class core__init.class
under target/classes/clj_genclass
.
Lookin' fresh
The resulting class isn't very convenient to work with, however. That's where :gen-class
come in: it provides a large number of options1 that make the generated class more like the Java library classes we interop with.
To automatically compile classes before starting a Lein REPL, the namespace needs to be added under :aot in project.clj
. Consider the following:
(ns clj-genclass.MyClass (:gen-class :constructors {[String] []} :init init :state state :methods [[print [] void]])) (defn -init [txt] [[] txt]) (defn -print [this] (println (.state this)))
Running lein repl
, we see that the class is compiled:
$ lein repl Compiling clj-genclass.MyClass nREPL server started on port 57734 on host 127.0.0.1 - nrepl://127.0.0.1:57734
And can be immediately imported and used:
clj-genclass.core=> (import 'clj_genclass.MyClass) clj_genclass.MyClass clj-genclass.core=> (.print (MyClass. "My own class")) My own class
The function is dead, long live the function
If we update MyClass
's -print
method, e.g.
(defn -print [this] (println "foo" (.state this)))
We can see the changes take place either by compiling the class in the REPL:
clj-genclass.core=> (compile 'clj-genclass.MyClass) clj-genclass.MyClass clj-genclass.core> (import 'clj_genclass.MyClass) clj_genclass.MyClass clj-genclass.core> (.print (MyClass. "My own class")) foo My own class
Or just evaluating it in CIDER. lein compile
does not work because it only writes to classfiles and doesn't load anything into memory.
The old switcheroo
Classes in Java are loaded by instances of ClassLoader2. Once an instance of ClassLoader
has loaded a class, it must always return what it already loaded. This means that it cannot reload a class whose definition has changed.
To get around this, Clojure takes advantage of the fact that a class is distinguished not just by its name but also by its classloader. Every time a form is (re)evaluated, a new instance of Clojure's own DynamicClassLoader is used. We can see this in action:
clj-genclass.core=> (defn foo [] "foo") #'clj-genclass.core/foo clj-genclass.core=> (.getClassLoader (.getClass foo)) #object[clojure.lang.DynamicClassLoader 0x41f045a1 "clojure.lang.DynamicClassLoader@41f045a1"] clj-genclass.core=> (defn foo [] "bar") #'clj-genclass.core/foo clj-genclass.core=> (.getClassLoader (.getClass foo)) #object[clojure.lang.DynamicClassLoader 0x32035f8f "clojure.lang.DynamicClassLoader@32035f8f"]
When foo
was redefined, the new version was loaded by a different classloader. All instances of DynamicClassLoader
share a cache so they can all find previously loaded classes. Classes that are no longer used (such as those that have been redefined) are eventually removed.
Footnotes:
:gen-class
by itself does not compile a class–it only specifies how a class should be compiled.
Except for the classes of the Java Runtime itself, which are loaded by a boostrap classloader written written in native code.