Thursday, December 15, 2016

Subclassing Custom Java Class in Clojure

Here are the ways to subclass a your custom Java class from Clojure. I have been fiddling to get this to work with lein for some time now and SO to the rescue of course. In your lein project say you have Java source at src/java. You want to extend a class from clojure, call super class method etc. There are two ways to extend, proxy and gen-class.
It's important to note that you need to have a package structure for the Java classes. Naked classes don't work because it's getting qualified to java.lang namespace when you try to run giving a ClassNotFoundException.
// src/java/com/example/BaseClass.java
package com.example;

public class BaseClass {
public String greet() {
return "Hello from BaseClass";
}
}
You need to specify the Java source location using :java-source-paths in project.clj. If you are using :gen-class to extend, then you need to aot compile your clojure file. When running project with :java-source-paths added under Windows, I am getting the following error even if JAVA_HOME is set to JDK location and bin is in path.
Java compiler not found; Be sure to use java from a JDK
rather than a JRE by modifying PATH or setting JAVA_CMD.
Adding JAVA_CMD env variable to point to java_home-path\bin\java works.
; project.clj
(defproject subclass "0.1.0-SNAPSHOT"
:dependencies [[org.clojure/clojure "1.8.0"]]
:java-source-paths ["src/java"]
:main subclass.core
:target-path "target/%s"
:aot [subclass.core]
:profiles {:uberjar {:aot :all}})
Extending using :gen-class use :extends keyword. If you want to call a super class method, use :expose-methods to specify an alias under which that method will be available locally. Here the greet from BaseClass is locally referred as pgreet
; src/subclass/core.clj
(ns subclass.core
(:gen-class
:extends com.example.BaseClass
:exposes-methods {greet pgreet}))

;; greet method override. gen-class prefixes generated classes with '-' by default.
(defn -greet [this]
;this arg is the object of this class, i.e., subclass.core.
(.pgreet this) ;calls super class' greet()
(println "hi from clj"))

(defn -main [& args]
(.greet (subclass.core.)))
Running the above gives
$ lein run
subclass.core=> (-main)
Hello from BaseClass
hi from clj
nil
Extending using proxy.
; src/subclass/core.clj
(ns subclass.core
(:import com.example.BaseClass))

(defn my-greet []
(proxy [BaseClass] [] ;the class to extend
;override greet()
(greet []
(proxy-super greet) ;call super class method
(println "hi from my-greet"))))

(defn -main [& args]
(.greet (my-greet))) ;calling my-greet returns a proxy object. Invoke greet method on it.
Running the proxy version gives
$ lein repl
subclass.core=> (-main)
Hello from BaseClass
hi from my-greet
nil
Using proxy or gen-class for extending depends on your use case.