Search code examples
javainteropclojureaot

How to pass a typed collection from clojure to java?


I know the basics of clojure/java interop: calling java from clojure and vice versa. However, I was not able to return a typed collection from clojure to java. I am trying to see something of that nature List<TypedObject> from the java code which is calling into clojure.

Java Object:

public class TypedObject {
    private OtherType1 _prop1;
    public OtherType1 getProp1() {
        return _prop1;
    }
    public void setProp1(OtherType1 prop1) {
        _prop1 = prop1;
    }
}

CLojure method:

(defn -createListOfTypedObjects
      "Creates and returns a list of TypedObjects"
      [input]
      ;Do work here  to create and return list of TypedObjects
      [typedObj1, typedObj2, typedObj3])

(:gen-class
 :name some.namespace
 :methods [createListofTypedObjects[String] ????])

Let us consider that I am writing an API using clojure, which is to be distributed as a jar file, to be used from java. My question was really how to what to pass in place of the ???? questions marks above inside the :gen-class for AOT, so that a programmer writing a piece of code in java using my api, can have the appropriate intellisense / code completion (i.e.: createListofTypedObjects() returns List<TypedObject>) from within eclipse for example.


Solution

  • The others are right that Clojure doesn't ensure the types of elements in returned collections, etc. (Actually, the JVM doesn't ensure the types of elements in collections, either – that's handled entirely by javac.)

    However, I can see the value of providing an API to other Java programmers that specifies an interface that declares that return values (or parameters) parameterized in various ways; this is especially attractive if one is looking to use Clojure in an existing Java environment without making waves.

    This currently requires a two step process:

    • define a separate interface (in Java!) that specifies the parameterized types as you like
    • define your gen-class namespace (or proxy or reify instance) such that it implements that interface

    (Clojure does provide a definterface form that would allow you to avoid the separate Java interface definition, but definterface, just like the rest of Clojure, does not provide for specifying parameterized types. Maybe someday... :-))

    e.g.

    public interface IFoo {
        List<TypedObject> createListOfTypedObjects ();
    }
    

    and then your gen-class namespace:

    (ns your.ns.FooImpl
      (:gen-class
        :implements [IFoo]))
    (defn -createListOfTypedObjects
      []
      [typedObj1, typedObj2, typedObj3])
    

    When your users create instances of FooImpl, they'll e.g. get code completion indicating that the method returns List<TypedObject> rather than Object or the unparameterized List type.

    If you're using sane build tools (e.g. maven, gradle, or properly-configured ant), then you can put the Java interface in your Clojure project, and the cross-language dependency will be taken care of.