Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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.

like image 995
user258030 Avatar asked Sep 10 '10 22:09

user258030


People also ask

Is clojure interoperable with Java?

Overview. Clojure was designed to be a hosted language that directly interoperates with its host platform (JVM, CLR and so on). Clojure code is compiled to JVM bytecode. For method calls on Java objects, Clojure compiler will try to emit the same bytecode javac would produce.

Can clojure use Java libraries?

Clojure programs get compiled to Java bytecode and executed within a JVM process. Clojure programs also have access to Java libraries, and you can easily interact with them using Clojure's interop facilities.

Does Clojure have generics?

generic defines a set of related multimethods that have the same names as their type-specific counterparts in clojure. core. Implementations for standard Clojure data types are provided and behave identically to the clojure.


3 Answers

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.

like image 133
cemerick Avatar answered Oct 11 '22 01:10

cemerick


If you're trying to pass something like List<String> to a java method, then you don't need to worry about it. The type parameter (e.g., String) is only used to by the javac compiler, so any List will work just fine at runtime.

On the other hand if you're trying to pass an array of a particular object type (e.g., String[]), then you can use the various -array functions:

user=> (make-array String 10)            ; an empty String array
#<String[] [Ljava.lang.String;@78878c4c>
user=> (into-array ["foo" "bar"])        ; array type inferred from first element
#<String[] [Ljava.lang.String;@743fbbfc>
user=> (into-array Number [1.2 5 7N])    ; explicit type array
#<Number[] [Ljava.lang.Number;@7433b121>
like image 36
Alex Taggart Avatar answered Oct 11 '22 03:10

Alex Taggart


You don't need to worry about generics (typed collections) in Clojure. Generics are really just type hints to the Java compiler. In a running Java program, List<String> is effectively the same as List<Object>.

So, for example, a Clojure vector containing Strings is already a List<String> with no conversion needed.

like image 9
Stuart Sierra Avatar answered Oct 11 '22 02:10

Stuart Sierra