Clojure has a very tight integration with Java. Direct use of the Java library in a Clojure application is completely simple and commonplace. Reverse integration is somewhat more complicated. This article outlines some options for integrating Clojure code into a Java application.
As an example, take the following code:
(ns clj-lib.core (:use clj-lib.util)) (defn prod ([x] (reduce * x)) ([sx] (reduce * sx))) (defprotocol IAppendable (append [this value])) (extend-protocol IAppendable Integer (append [this value] (+ this value)) String (append [this value] (str this "," value))) (defmulti pretty type) (defmethod pretty Integer [x] (str "int " x)) (defmethod pretty String [x] (str "str " x))
There are no global variables, if necessary, you can create separate get-functions for them. Multimethod and protocol declared - they can also be used from Java.
Standard Java Interfaces
Clojure uses its own implementation of standard structures, with its own interfaces. To heighten convenience, all
standard collections implement interfaces from
java.util. * . For example, all sequences (lists, vectors, even lazy sequences) implement the
java.util.List interface. Of course, all the “mutated” methods (
add ,
clear , etc.) simply throw an
UnsupportedOperationException exception. Similarly with sets and dictionaries - they implement
Set and
Map respectively.
')
All functions implement 2 standard
java.lang.Runnable and
java.util.concurrent.Callable interfaces. This can be handy when working directly with
java.lang.concurent (although, most likely, it is better to work with executors directly from Clojure).
When working with long arithmetic, it is important to remember that Clojure has its own type for long integers
clojure.lang.BigInt . At the same time, Clojure can work with standard
java.util.math.BigInteger . There are no such “surprises” with long material ones - the standard
java.util.math.BigDecimal is used. There is also a special type of
clojure.lang.Ratio for rational fractions.
Compile and gen-class
Probably the most obvious option is to
compile a clojure code and get a set of class files. Add the
gen-class command to our namespace declaration, specify the signatures for the functions:
(ns clj-lib.core (:use clj-lib.util) (:gen-class :main false :name "cljlib.CljLibCore" :prefix "" :methods [^:static [prod [Iterable] Number] ^:static [prod [Number Iterable] Number] ^:static [append [Object Object] Object] ^:static [pretty [Object] Object]])) ...
Here we specified
Iterable as the type of the argument for the
prod function. In fact, you can pass there both
ISeq , and an array, and even a
String . But, most likely, in Java it will be more convenient to work with this interface.
You can select any class name.If the parameter is not specified, then clj_lib.core will be used. For the protocol, the class clj_lib.core.IAppendable will be generated in the clj_lib.core package. That is, we will have a class and a package with the same name. Therefore, it is better to specify the class name explicitly.
After that you need to compile the namespace. Perform at the REPL:
(compile 'clj-lib.core)
We
get the classes / cljlib / CljLibCore.class file , which can be directly used in our application. But compiling from the REPL is frankly inconvenient, therefore it is better to set up a
leiningen project:
(defproject clj-lib ... :aot [my-app.core], ... )
Now when creating a jar-ok, leiningen will automatically compile the specified namespace. Execute the command:
lein jar
We connect my-lib.jar and clojure.jar to our Java project and use:
import java.math.BigDecimal; import java.util.Arrays; import java.util.List; import clj_lib.core.IAppendable; import cljlib.CljLibCore; public class Program { static void pr(Object v) { System.out.println(v); } static class SomeClass implements IAppendable { public Object append(Object value) { // some code return null; } } public static void main(String[] args) throws Exception { pr(CljLibCore.pretty(1)); pr(CljLibCore.pretty("x")); Integer x = (Integer) CljLibCore.append(-1, 1); pr(CljLibCore.append(x, 1)); pr(CljLibCore.append(new SomeClass(), new SomeClass())); List<Integer> v = Arrays.asList(1, 2, 3, 4, 5); pr(CljLibCore.prod(v)); pr(CljLibCore.prod(BigDecimal.ONE, v)); } }
When the class is loaded, runtime Slojure will be automatically initialized - no additional actions are required. It is also important to note that we can extend all protocols directly from Java - we just need to implement the appropriate interface. But it is still better to work with objects through functions, rather than invoking interface protocol methods. Otherwise, the
extend-protocol will not work - very badly.
Perhaps the main disadvantage of this approach is the need for compilation as such. The documentation for functions is also not available from the IDE, you need to adapt the source code of the library (or make a binding on Clojure). On the other hand, in some specific cases, the only option is to have an “honest” class file in the classpath.
Use clojure.lang.RT
The heart of all runtime Slojure is this class. It defines static methods for creating keywords, symbols, vectors, implementing basic functions (for example,
first and
rest ) and much more. Class undocumented, does not have a permanent interface - use at your own risk. But there are several useful functions that make integration as simple as possible:
- Var var (String ns, String name) - returns a var-cell by full name;
- Keyword keyword (String ns, String name) - returns a keyword (the first parameter can be null);
- void load (String path) - loads a clj script at the specified path.
And many more, easier to turn to
implementation . You can call an arbitrary function like this:
RT.var("clojure.core", "eval").invoke("(+ 1 2)");
Rewrite the above example using the
RT class:
import java.math.BigDecimal; import clojure.lang.RT; import clojure.lang.Sequential; import clojure.lang.Var; public class Program { static void pr(Object v) { System.out.println(v); } public static void main(String[] args) throws Exception { Var pretty = RT.var("clj-lib.core", "pretty"); Var append = RT.var("clj-lib.core", "append"); Var prod = RT.var("clj-lib.core", "prod"); pr(pretty.invoke(1)); pr(pretty.invoke("x")); Integer x = (Integer) append.invoke(-1, 1); pr(append.invoke(x, 1)); Sequential v = RT.vector(1, 2, 3, 4, 5); pr(prod.invoke(v)); pr(prod.invoke(BigDecimal.ONE, v)); } }
Now we can no longer extend the protocol directly from Java - the
IAppendable interface is created in runtime and is not available when compiling our java file. But there is no need for AOT.
Java interface and reify
In fact, this is not a separate method, but rather an option, how to arrange interaction with the library. Writing Java interface:
public interface ICljLibCore { Number prod(Iterable x); Number prod(Number s, Iterable x); Object append(Object self, Object value); String pretty(Object x); }
After that in Clojure we create a similar function:
(defn get-lib [] (reify ICljLibCore (prod [_ x] (prod x)) (prod [_ sx] (prod x)) (append [_ tv] (append tv)) (pretty [_ x] (pretty x))))
When accessing the library, we create an instance of ICljLibCore and write it in a static field:
public static final ICljLibCore CLJ_LIB_CORE = (ICljLibCore) RT.var("clj-lib.core", "get-lib").invoke(); ... CLJ_LIB_CORE.pretty("1");
Lack of approach - you have to manually create the interface. On the other hand, honest java-doc can be placed in this interface. It will be easier to replace Clojure with a Java implementation of the library (if you suddenly miss speed), it is easier to write tests. And, of course, no one AOT.
Conclusion
Outside the article there are some alternative options. For example, automatically generate wrapper classes based on Clojure code and meta data (and arrange it as a leiningen plugin). You can make transparent integration into a DI framework (for example, Spring or Guice). There are many options, with their pros and cons.