I recently ran into some hiccups while trying to implement a Java interface in Clojure. While it the documentation my make it appear seamless, there are a few quirks. I'll explain the different ways I've found of implementing an interface and the hangups/quirks of each.
(Note: I'll be implementing an Interface called "RequestHandler", which is part of a namespace called "HttpServer")
:gen-class
The unfortunate truth about this method is that it requires AOT compilation. So, in order to get this example to compile, you must add :aot [implement-java-interface.first-request-handler]
to your project.clj
file. Here's what it looks like.
(ns implement-java-interface.first-request-handler
(:gen-class
:implements [HttpServer.RequestHandler]))
(defn -canRespond [this request]
true)
(defn -getResponse [this request]
nil)
implement-java-interface.main=> (.canRespond (implement_java_interface.first_request_handler.) nil)
true
Looks pretty simple right? The first thing to notice is that to instantiate the class I had to call implement_java_interface.first_request_handler
instead of implement-java-interface.first-request-handler
. Because of the way that Clojure resolves namespaces, any dashes in namespaces are converted to underscores for file names. Therefore, we generated a class with underscores. There are a few ways to fix this. First, we can use the :name
parameter of :gen-class
(ns implement-java-interface.first-request-handler
(:gen-class
:name implement-java-interface.first-request-handler
:implements [HttpServer.RequestHandler]))
(defn -canRespond [this request]
true)
(defn -getResponse [this request]
nil)
implement-java-interface.main=> (.canRespond (implement-java-interface.first-request-handler.) nil)
true
Second, we can write a constructor method in the namespace,
(ns implement-java-interface.first-request-handler
(:gen-class
:implements [HttpServer.RequestHandler]))
(defn -canRespond [this request]
true)
(defn -getResponse [this request]
nil)
(defn new-first-request-handler []
(implement_java_interface.first_request_handler.))
implement-java-interface.main=> (use 'implement-java-interface.first-request-handler)
nil
implement-java-interface.main=> (.canRespond (new-first-request-handler) nil)
true
This is the preferred method. It allows us to use more Clojure-like constructs to create an object, i.e. calling a method within a namespace as opposed to calling a constructor on a class.
deftype/defrecord
Using deftype or defrecord is certainly a little cleaner than :gen-class
and it doesn't require AOT compilation. Here's what it looks like.
(ns implement-java-interface)
(deftype second-request-handler []
HttpServer.RequestHandler
(canRespond [this request] true)
(getResponse [this request] nil))
implement-java-interface.main=> (require 'implement-java-interface.second-request-handler)
nil
implement-java-interface.main=> (.canRespond (implement_java_interface.second-request-handler.) nil)
true
Once again, our clean code is befuddled by the way Clojure resolves namespaces. Let's fix this by putting a method on the namespace.
(ns implement-java-interface.second-request-handler)
(deftype second-request-handler []
HttpServer.RequestHandler
(canRespond [this request] true)
(getResponse [this request] nil))
(defn new-second-request-handler []
(second-request-handler.))
implement-java-interface.main=> (use 'implement-java-interface.second-request-handler)
nil
implement-java-interface.main=> (.canRespond (new-second-request-handler) nil)
true
Excellent! We were able to use a method on the namespace to construct the class for us, just like the first example. Plus, this method doesn't require AOT!
reify/proxy
Last, but not least, let's use the reify
feature of Clojure to implement an interface.
(ns implement-java-interface.third-request-handler)
(defn new-third-request-handler []
(reify
HttpServer.RequestHandler
(canRespond [this request] true)
(getResponse [this request] nil)))
implement-java-interface.main=> (use 'implement-java-interface.third-request-handler)
nil
implement-java-interface.main=> (.canRespond (new-third-request-handler) nil)
true
As you can see, reify
is the cleanest of all the examples and works like we want it too right away.
Conclusion
Of the three, I would have to say that it's a close call between deftype
and reify
, but reify
is my favorite. The two methods are very similar, however deftype
is a little confusing with the namespace issues and constructing, but reify
has the same benefit of not using AOT and is very succinct and clean as well. So, in the end, I will be using reify
.