Java Interoperability
Create New Instance Of Java Class
Working with Java classes from Clojure code is easy. If we want to invoke methods or access fields on instances of Java classes we must first create an instance by invoking the constructor. In Clojure we can do that using the special form new or using a dot (.) notation. The new special form has the class name of the Java class we want to create an instance of as argument followed by arguments for the Java class constructor. With the dot notation we place a . after the class name followed by arguments for the class constructor to create a new instance.
In the following example we see several ways to create an instance of a Java class.
(ns mrhaki.java.new-instance
(:require [clojure.test :refer [is]])
(:import (java.net URI)
(java.util Map TreeMap)))
;; Using a dot after the class name to invoke the constructor.
(is (instance? String (String.)))
;; Or using the new special form to invoke the constructor.
(is (instance? String (new String)))
;; Constructor arguments can be used.
(is (instance? URI (URI. "https://www.mrhaki.com")))
(is (instance? URI (new URI "https" "www.mrhaki.com" "/" "")))
;; We can use Clojure data structures in constructors.
(is (instance? Map (TreeMap. {:language "Clojure"})))
Written with Clojure 1.10.1.
Original post written on March 18, 2021
Create And Initialize Object Based On Java Class With doto
It is very easy to work with Java classes in Clojure. If we want to create a new object based on a Java class and invoke methods to initialize the object directly we can use the doto macro. The first argument is an expression to create a new object and the rest of the arguments are functions to invoke methods on the newly created object. The object returned from the first argument is passed as first argument to the method invocations. The doto function returns the object that is created with the first argument.
In the following example code we use the doto function in several cases:
(ns mrhaki.core.doto
(:require [clojure.test :refer [is]]))
;; With doto we can invoke functions on object returned
;; by the first argument, where the object is passed
;; before the given arguments.
(def sb (doto (StringBuilder.)
(.append "one")
(.append "two")
(.reverse)))
(is (= "owteno" (.toString sb)))
;; We can use functions in doto. For example
;; to create a value for the function invocation
;; on the type of the first argument.
(def sample (doto (new StringBuilder)
(.append "{")
(.append (apply str (repeat 10 "a")))
(.append "}")))
(is (= "{aaaaaaaaaa}" (.toString sample)))
;; Type returned is the same as result of evaluation
;; of first argument, not the last argument.
(is (instance? StringBuilder (doto (StringBuilder.) (.toString))))
Written with Clojure 1.10.1.
Original post written on July 13, 2020
Use .. For Invocation Java Method Chaining
Accessing Java from Clojure is easy. With the dot (.) special form we can invoke for example methods from a Java class or instance. If we want to invoke several methods together where the return value from one method is used to invoke the next method (method chaining) we can use the .. macro. The macro will expand into a nested expression with the . forms.
In the following example we see how to use the .. macro and how we can achieve the same result using nested . expressions and by using the thread first macro:
(ns mrhaki.java
(:require [clojure.test :refer [is]])
(:import (java.util Optional)))
(def value "Clojure")
;; We use Optional map method that accepts a java.util.function.Function,
;; so here we implement the Function interface with an implementation
;; to return the given String value in upper case.
(def fn-upper
(reify java.util.function.Function
(apply [this arg] (. arg toUpperCase))))
(is (= "CLOJURE"
;; Invoke Java method chaining using the special .. macro.
;; Java: Optional.ofNullable(value).map(s -> s.toUpperCase()).orElse("Default")
(.. Optional (ofNullable value) (map fn-upper) (orElse "Default"))
;; Macro expands to the following equivalent using . form.
(. (. (. Optional ofNullable value) (map fn-upper)) (orElse "Default"))
;; Using thread first macro with equivalent method invocations.
(-> (Optional/ofNullable value)
(. (map fn-upper))
(. (orElse "Default")))))
(is (= "Default"
(.. Optional (ofNullable nil) (map fn-upper) (orElse "Default"))))
Written with Clojure 1.10.1.
Original post written on February 3, 2021
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.
Original post written on January 28, 2021
Turn Java Object To Map With bean Function
The map data structure is used a lot in Clojure. When we want to use Java objects in our Clojure code we can convert the Java object to a map with the bean function. This function will use reflection to get all the properties of the Java object and converts each property with the property value to a key with value in the resulting map. The bean function will not recursively convert nested objects to a map, only the top-level properties are turned into key value pairs.
We see several examples of using the bean function in the following code snippet:
(ns mrhaki.core.bean
(:require [clojure.test :refer [is]])
(:import (java.net URI)
(mrhaki.java Person Address)))
(is (= {:path "",
:rawQuery "search=clojure",
:fragment nil,
:authority "www.mrhaki.com",
:rawAuthority "www.mrhaki.com",
:port -1,
:absolute true,
:host "www.mrhaki.com",
:rawPath "",
:opaque false,
:rawSchemeSpecificPart "//www.mrhaki.com?search=clojure",
:class java.net.URI,
:rawUserInfo nil,
:query "search=clojure",
:rawFragment nil,
:scheme "https",
:userInfo nil,
:schemeSpecificPart "//www.mrhaki.com?search=clojure"}
(bean (URI. "https://www.mrhaki.com?search=clojure"))))
(comment "For the next sample we use a Java class Person
with properties name and alias of type String and
a third property of type Address. The Java class Address
has a single String property city.
Pseudo code:
class Person { String name, alias; Address address; }
class Address { String city; }")
(def person (Person. "Hubert" "mrhaki" (Address. "Tilburg")))
(is (= {:name "Hubert" :alias "mrhaki"}
(select-keys (bean person) [:name :alias])))
;; Properties with custom classes are not automatically
;; also converted to a map representation.
(is (instance? mrhaki.java.Address (:address (bean person))))
(is (= {:city "Tilburg"}
(select-keys (bean (:address (bean person))) [:city])))
(is (= {:address {:city "Tilburg", :class mrhaki.java.Address},
:alias "mrhaki",
:class mrhaki.java.Person,
:name "Hubert"}
(assoc (bean person) :address (bean (.getAddress person)))))
Written with Clojure 1.10.1.
Original post written on July 14, 2020