How to pass a typed collection from clojure to java?
Asked Answered
B

3

17

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.

Buell answered 10/9, 2010 at 22:12 Comment(2)
a short example of the java code that calls clojure would really help me anser this :)Fugacious
Thank you Alex and Stuart for your answers. They make perfect sense, but not quite what I was looking for. Hopefully, my question is now less ambiguous.Buell
I
23

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.

Inquisitorial answered 22/9, 2010 at 14:39 Comment(1)
V helpful answer. It is key importance to provide proper strongly typed definitions for running in java env. Seldom do types not appear after all since most work involves collections and usually we want to specify what they contain.Tellurian
G
10

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>
Galactometer answered 11/9, 2010 at 1:12 Comment(0)
A
9

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.

Arana answered 11/9, 2010 at 1:13 Comment(2)
Not true. I would say more precisely, you usually don't need to worry about typed collections in Clojure. Since this question is specifically dealing with Java interop it's important to point out that the give collection may at some point be passed back to a Java method. In this case the type of the elements in the collection may be important if the method name is overloaded taking collections with different element types. I understand this may be obscure and uncommon... but it does exist.Albers
I do not know a way to denote precise instantiations of generics in Clojure. For example, one can write java.lang.ArrayList but not java.langArrayList<String>. If you're right, @Jason, could this lack be a hole in Clojure-Java interop? Could there be APIs in Java that are not callable from Clojure because covariant collection types are not denotable? The answers above and below suggests not, i.e., that we're ok, at least for now.Corody

© 2022 - 2024 — McMap. All rights reserved.