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 theArrayListclass from thejava.utilpackage.(def my-list (ArrayList.))Here we are defining a variablemy-listwhich is an instance of theArrayListclass.(.add my-list "Hello")This statement is calling the instance methodaddonmy-listand passing the string “Hello” to it. This will add “Hello” to the ArrayList.(.add my-list "World")This is similar to the previousaddcall but it’s adding the string “World” to the ArrayList.(.size my-list)Here we are calling thesizemethod onmy-listwhich will return the number of elements in the ArrayList.(import 'java.awt.Dimension)This statement is importing theDimensionclass from thejava.awtpackage.(def dim (Dimension. 10 20))This defines a variabledimwhich is an instance of theDimensionclass initialized with width 10 and height 20.(.width dim)and(.height dim)These are calling thewidthandheightfields of thedimobject which will return the width and height of the dimension, respectively.(import 'java.util.UUID)This statement is importing theUUIDclass from thejava.utilpackage.(UUID/randomUUID)Here we’re calling the static methodrandomUUIDof theUUIDclass 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.coreand importing theclojure.reflectlibrary.
- 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.14into 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-arrayobject. 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-arrayback into a Clojure sequence(1 2 3).
- This function converts the Java array
(def java-string (String. "Hello"))- This line defines a variable
java-stringand initializes it to a new instance of the JavaStringclass with the value"Hello".
- This line defines a variable
(class java-string)- This function gets the class of the
java-stringobject. The result isjava.lang.String.
- This function gets the class of the
(.length java-string)- This line calls the
lengthmethod on thejava-stringobject. The result is5, which is the length of the string"Hello".
- This line calls the
(str java-string " World")- This function concatenates the
java-stringobject 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.coreand includes theclojure.reflectlibrary (with the aliasreflect). Theclojure.reflectlibrary 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 anArithmeticExceptionin Java. - Exception Handling (
catch):(catch ArithmeticException e (str "Exception name: " e " >>> Message: " (.getMessage e)))When theArithmeticExceptionis thrown, thecatchblock catches it. In this block, the exceptioneis caught and the name of the exception along with its message is concatenated into a string. The.getMessagefunction 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 thecatchblock 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 thecatchblock.
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.reflectlibrary 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
Stringsclass from the Google Guava library.
- This line is importing the
(Strings/repeat "Hello" 3)- It uses the
repeatmethod from theStringsclass 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
titleCasemethod from theStringsclass.
- It converts the string “hello world” to title case, using the
(import 'com.google.common.base.Preconditions)- This line is importing the
Preconditionsclass 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,
numeratoranddenominator. It checks the precondition that the denominator is not zero, using thecheckArgumentmethod from thePreconditionsclass. If the precondition fails, it will throw anIllegalArgumentExceptionwith 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
dividefunction with 42 as the numerator and 0 as the denominator. Since dividing by zero is not allowed by thedividefunction, it will throw anIllegalArgumentException. Thecatchblock 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
reflectfunction from theclojure.reflectlibrary to reflect on theStringsclass, i.e., to examine its structure and metadata at runtime. It then uses thekeysfunction to retrieve the keys of the map returned by thereflectfunction. These keys represent the top-level categories of information available via reflection for theStringsclass.
- 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)
;; => 3Explanation
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.reflectmodule under the aliasreflect. - The
(import 'interop_java_in_clojure.Calculator)line is importing a Java class namedCalculatorfrom theinterop_java_in_clojurepackage. - The
(def calculator (Calculator.))line is defining a new instance of theCalculatorclass and binding it to the symbolcalculator. - The
(class calculator)line retrieves the class of thecalculatorinstance, which isinterop_java_in_clojure.Calculator. - The
(.superAdd calculator 1 2)line is calling thesuperAddmethod of thecalculatorinstance, passing1and2as arguments. This function assumes thatCalculatorhas asuperAddmethod 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