Interop: Using Java in Clojure
Interop Basics
Code
(ns interop-using-java-in-clojure.core
(:require
[clojure.reflect :as reflect]))
;; ---
;; Interop basics
;; ---
;; Call instance method
(import 'java.util.ArrayList)
(def my-list (ArrayList.))
my-list
;; => []
(.add my-list "Hello")
;; => true
my-list
;; => ["Hello"]
(.add my-list "World")
;; => true
my-list
;; => ["Hello" "World"]
(.size my-list)
;; => 2
;; Call fields
(import 'java.awt.Dimension)
(def dim (Dimension. 10 20))
dim
;; => #object[java.awt.Dimension 0x48d8fc0 "java.awt.Dimension[width=10,height=20]"]
(.width dim)
;; => 10
(.height dim)
;; => 20
;; Call static method
(import 'java.util.UUID)
(UUID/randomUUID)
;; => #uuid "467c1c23-5a96-4e09-bcbe-497abcb6d73f"
Explanation
Here’s the explanation for the provided Clojure code:
(ns interop-java-in-clojure.core (:require [clojure.reflect :as reflect]))
This statement is defining a namespace called “interop-java-in-clojure.core”. It’s also requiring a library called “clojure.reflect” and aliasing it as “reflect”.(import 'java.util.ArrayList)
This statement is importing theArrayList
class from thejava.util
package.(def my-list (ArrayList.))
Here we are defining a variablemy-list
which is an instance of theArrayList
class.(.add my-list "Hello")
This statement is calling the instance methodadd
onmy-list
and passing the string “Hello” to it. This will add “Hello” to the ArrayList.(.add my-list "World")
This is similar to the previousadd
call but it’s adding the string “World” to the ArrayList.(.size my-list)
Here we are calling thesize
method onmy-list
which will return the number of elements in the ArrayList.(import 'java.awt.Dimension)
This statement is importing theDimension
class from thejava.awt
package.(def dim (Dimension. 10 20))
This defines a variabledim
which is an instance of theDimension
class initialized with width 10 and height 20.(.width dim)
and(.height dim)
These are calling thewidth
andheight
fields of thedim
object which will return the width and height of the dimension, respectively.(import 'java.util.UUID)
This statement is importing theUUID
class from thejava.util
package.(UUID/randomUUID)
Here we’re calling the static methodrandomUUID
of theUUID
class which generates a random UUID and returns it.
Java Data Types
Code
(ns interop-using-java-in-clojure.core
(:require
[clojure.reflect :as reflect]))
;; ---
;; Java data types
;; ---
(int 42)
;; => 42
(double 3.14)
;; => 3.14
(+ (int 42) (double 3.14))
;; => 45.14
(def java-array (to-array [1 2 3]))
(class java-array)
;; => [Ljava.lang.Object;
(seq java-array)
;; => (1 2 3)
(def java-string (String. "Hello"))
;; => #'interop-java-in-clojure.core/java-string
(class java-string)
;; => java.lang.String
(.length java-string)
;; => 5
(str java-string " World")
;; => "Hello World"
Explanation
This Clojure code is demonstrating how to work with Java data types and objects in Clojure. Clojure is a hosted language that can interoperate with its host platform, such as JVM (Java Virtual Machine). This allows Clojure to interact with Java libraries and utilize its functionality. Here’s a breakdown of the code:
(ns interop-java-in-clojure.core (:require [clojure.reflect :as reflect]))
- This line is defining a new namespace named
interop-java-in-clojure.core
and importing theclojure.reflect
library.
- This line is defining a new namespace named
(int 42)
- This function converts the number 42 into an integer. The result is
42
.
- This function converts the number 42 into an integer. The result is
(double 3.14)
- This function converts the number
3.14
into a double-precision floating-point number. The result is3.14
.
- This function converts the number
(+ (int 42) (double 3.14))
- This line adds together an integer (
42
) and a double (3.14
). Clojure will automatically coerce the integer to a double before performing the addition, so the result is45.14
.
- This line adds together an integer (
(def java-array (to-array [1 2 3]))
- This line defines a variable
java-array
, converting the Clojure vector[1 2 3]
into a Java array.
- This line defines a variable
(class java-array)
- This function gets the class of the
java-array
object. The result,[Ljava.lang.Object;
, is the internal notation used by the JVM to represent an array of objects.
- This function gets the class of the
(seq java-array)
- This function converts the Java array
java-array
back into a Clojure sequence(1 2 3)
.
- This function converts the Java array
(def java-string (String. "Hello"))
- This line defines a variable
java-string
and initializes it to a new instance of the JavaString
class with the value"Hello"
.
- This line defines a variable
(class java-string)
- This function gets the class of the
java-string
object. The result isjava.lang.String
.
- This function gets the class of the
(.length java-string)
- This line calls the
length
method on thejava-string
object. The result is5
, which is the length of the string"Hello"
.
- This line calls the
(str java-string " World")
- This function concatenates the
java-string
object with the string" World"
. The result is"Hello World"
.
- This function concatenates the
Java Exception Handling
Code
(ns interop-java-in-clojure.core
(:require
[clojure.reflect :as reflect]))
;; ---
;; Java exception handling
;; ---
(try (/ (int 42) (int 0))
(catch ArithmeticException e
(str "Exception name: " e " >>> Message: " (.getMessage e))))
;; => "Exception name: java.lang.ArithmeticException: Divide by zero >>> Message: Divide by zero"
Explanation
This Clojure code is demonstrating how to interoperate with Java, specifically dealing with exception handling. Let’s go through it in more detail:
- Namespace Definition (
ns
):(ns interop-java-in-clojure.core (:require [clojure.reflect :as reflect]))
This line defines the namespaceinterop-java-in-clojure.core
and includes theclojure.reflect
library (with the aliasreflect
). Theclojure.reflect
library is used for accessing metadata of Clojure data structures and Java objects, though in this snippet it is not actually used. - Try/Catch block:
This block tries to execute a division operation (
(/ (int 42) (int 0))
), which attempts to divide 42 by 0. This is an undefined operation and it will throw anArithmeticException
in Java. - Exception Handling (
catch
):(catch ArithmeticException e (str "Exception name: " e " >>> Message: " (.getMessage e)))
When theArithmeticException
is thrown, thecatch
block catches it. In this block, the exceptione
is caught and the name of the exception along with its message is concatenated into a string. The.getMessage
function is a Java method that retrieves the detail message string of the exception. - Output:
"Exception name: java.lang.ArithmeticException: Divide by zero >>> Message: Divide by zero"
This is the output from thecatch
block when the exception is caught. It includes the type of the exception (java.lang.ArithmeticException
), the default message from the exception (Divide by zero
), and it’s printed out in the string format specified in thecatch
block.
In summary, this Clojure code demonstrates how to handle Java exceptions within Clojure, specifically the ArithmeticException
that is thrown when attempting to divide by zero.
Java External Libraries
Code
(ns interop-java-in-clojure.core
(:require
[clojure.reflect :as reflect]))
;; ---
;; Java external libraries
;; ---
(import 'com.google.common.base.Strings)
(Strings/repeat "Hello" 3)
;; => "HelloHelloHello"
(Strings/titleCase "hello world")
(import 'com.google.common.base.Preconditions)
(defn divide [numerator denominator]
(Preconditions/checkArgument (not= denominator 0) "Denominator must be non-zero")
(/ numerator denominator))
(try
(divide 42 0)
(catch IllegalArgumentException e
(str "Exception name: " e " >>> Message: " (.getMessage e))))
;; => "Exception name: java.lang.IllegalArgumentException: Denominator must be non-zero >>> Message: Denominator must be non-zero"
(keys (reflect/reflect Strings))
;; => (:bases :flags :members)
Explanation
Here’s the explanation for the provided Clojure code:
(ns interop-java-in-clojure.core (:require [clojure.reflect :as reflect]))
- This is the namespace declaration. It’s declaring a namespace called “interop-java-in-clojure.core” and it’s also requiring the
clojure.reflect
library and aliasing it asreflect
.
- This is the namespace declaration. It’s declaring a namespace called “interop-java-in-clojure.core” and it’s also requiring the
(import 'com.google.common.base.Strings)
- This line is importing the
Strings
class from the Google Guava library.
- This line is importing the
(Strings/repeat "Hello" 3)
- It uses the
repeat
method from theStrings
class to repeat the string “Hello” three times. It will return “HelloHelloHello”.
- It uses the
(Strings/titleCase "hello world")
- It converts the string “hello world” to title case, using the
titleCase
method from theStrings
class.
- It converts the string “hello world” to title case, using the
(import 'com.google.common.base.Preconditions)
- This line is importing the
Preconditions
class from the Google Guava library.
- This line is importing the
(defn divide [numerator denominator] (Preconditions/checkArgument (not= denominator 0) "Denominator must be non-zero") (/ numerator denominator))
- This function takes two parameters,
numerator
anddenominator
. It checks the precondition that the denominator is not zero, using thecheckArgument
method from thePreconditions
class. If the precondition fails, it will throw anIllegalArgumentException
with the message “Denominator must be non-zero”. If the precondition passes, it performs the division and returns the result.
- This function takes two parameters,
(try (divide 42 0) (catch IllegalArgumentException e (str "Exception name: " e " >>> Message: " (.getMessage e))))
- This block of code is trying to call the
divide
function with 42 as the numerator and 0 as the denominator. Since dividing by zero is not allowed by thedivide
function, it will throw anIllegalArgumentException
. Thecatch
block will then catch this exception, and return a string that contains the name of the exception and its message.
- This block of code is trying to call the
(keys (reflect/reflect Strings))
- This line uses the
reflect
function from theclojure.reflect
library to reflect on theStrings
class, i.e., to examine its structure and metadata at runtime. It then uses thekeys
function to retrieve the keys of the map returned by thereflect
function. These keys represent the top-level categories of information available via reflection for theStrings
class.
- This line uses the
Local Java File
Code
Calculator.java
package interop_java_in_clojure;
public class Calculator {
public int superAdd(int a, int b) {
return a + b;
}
}
core.clj
(ns interop-java-in-clojure.core
(:require
[clojure.reflect :as reflect]))
;; ---
;; Local Java file
;; ---
;; Assume that Calculator.class is in the correct folder/package
;; and has a superAdd method that adds two numbers
;; See the repository
(import 'interop_java_in_clojure.Calculator)
(def calculator (Calculator.))
;; => #'interop-java-in-clojure.core/calculator
(class calculator)
;; => interop_java_in_clojure.Calculator
(.superAdd calculator 1 2)
;; => 3
Explanation
This Clojure code is doing the following:
- The
(ns interop-java-in-clojure.core (:require [clojure.reflect :as reflect]))
line is declaring a namespace namedinterop-java-in-clojure.core
. It also imports theclojure.reflect
module under the aliasreflect
. - The
(import 'interop_java_in_clojure.Calculator)
line is importing a Java class namedCalculator
from theinterop_java_in_clojure
package. - The
(def calculator (Calculator.))
line is defining a new instance of theCalculator
class and binding it to the symbolcalculator
. - The
(class calculator)
line retrieves the class of thecalculator
instance, which isinterop_java_in_clojure.Calculator
. - The
(.superAdd calculator 1 2)
line is calling thesuperAdd
method of thecalculator
instance, passing1
and2
as arguments. This function assumes thatCalculator
has asuperAdd
method that adds two numbers. The expected result of this call is3
.
Further Readings
- Official Clojure’s Java Interop Reference: https://clojure.org/reference/java_interop