Dark theme | Light theme

January 28, 2021

Clojure Goodness: Invoke Java Method With Varargs Parameter

Sometimes we want to invoke Java methods from our Clojure code. If the Java method accepts a variable arguments (varargs) parameter and we want to invoke the method from Clojure we must pass an array as argument. To create an array in Clojure we can use several functions. The to-array function will transform a collection to an Object[] type. For primitive type arrays we can use for example int-array to get a int[] array. The function into-array is the most flexible function. This function accepts a sequence argument and optionally the class type of the resulting array. Once we have the array we can use it as argument value for the varargs parameter of the Java method we want to invoke.

In the following example we use into-array, to-array and short-array to invoke a Java method with varargs parameter and see how we can build different array types:

(ns mrhaki.core.varargs
  (:require [clojure.test :refer [is]])
  (:import (java.text MessageFormat)))

;; We want to invoke the Java method MessageFormat/format that accepts
;; a format parameter followed by a varargs parameter.
(is (thrown-with-msg? ClassCastException 
                      #"java.lang.String incompatible with \[Ljava.lang.Object;" 
                      (MessageFormat/format "{0} is awesome." "Clojure")))
;; Use into-array to transform sequence to Java array,
;; that can be used for methods that accept varargs parameter.
(is (= "Clojure is awesome."
       (MessageFormat/format "{0} is awesome."
                             (into-array ["Clojure"]))))

;; In the next example the type of the array is based on the first element
;; and becomes String[], but the sequence has also elements with other types.
;; We use the argment Object to have an Object[] array.
(is (= "Clojure contains 7 characters."
       (MessageFormat/format "{0} contains {1} characters."
                             (into-array Object ["Clojure" (count "Clojure")]))))

;; To get an Object[] array we could also use to-array function 
;; with a collection argument.
(is (= "Clojure contains 7 characters."
       (MessageFormat/format "{0} contains {1} characters."
                             (to-array ["Clojure" (count "Clojure")]))))

;; Type of first element sets array type.
(is (= "[Ljava.lang.String;"
       (.getName (class (into-array ["Clojure" "Groovy"])))))

;; Use explicit type or to-array function that always returns Object[] array.
(is (= "[Ljava.lang.Object;"
       (.getName (class (into-array Object ["Clojure" "Groovy"])))
       (.getName (class (to-array ["Clojure" "Groovy"])))))

;; Primitive types are transformed to array of boxed type: short, becomes Short.
(is (= "[Ljava.lang.Short;"
       (.getName (class (into-array (map short (range 5)))))))

;; We can get primitive type by using TYPE field of boxed type or
;; specific <primitive>-array function, like short-array.
(is (= "[S"
       (.getName (class (into-array Short/TYPE (map short (range 5)))))
       (.getName (class (short-array (range 5))))))

Written with Clojure 1.10.1.