Chapter 4. Java Interop and Polymorphism

As already mentioned, Clojure runs on the Java virtual machine, and it uses this to its advantage. Not only is the JVM a production-hardened platform to run on, being a JVM language gives Clojure access to many different Java libraries as well as its own. We will look at how Clojure talks to Java classes in this chapter. We will also look at another way that it benefits from using Java classes for some types of polymorphism and how Clojure handles this polymorphism more generally as well.

First, we will explore Java interop.

Handling Interop with Java

When a new language comes into being, it faces the library problem. That is, to be useful in everyday situations, a language needs to do all the things that current dominant languages do. These current dominant languages have a full array of libraries that support things like parsing JSON and logging.

Clojure solved this new language library problem by running on the JVM and having interoperability with Java classes. When you use Clojure, you can use Java classes and Java libraries. Clojure builds on the strength of the production-hardened and tested JVM and existing Java libraries. In fact, many of the popular Clojure libraries in use today utilize Java libraries as fundamental building blocks. We are going to cover the most common areas that you will encounter: how to import Java libraries/classes, how to create new instances of them, and how to interact with their methods.

Note

Don’t sweat it if you don’t have a Java background. We are just dipping our toes in. The water is fine here.

Clojure uses the new and dot special form to interact with Java classes, but provides more idiomatic forms that use them under the covers. We can take a look at this with one of Clojure’s strings. For example, let’s use the string "caterpillar", which is one of the characters that Alice met in Wonderland. A string is really just a string from Java—it is a java.lang.String.

Note

A String in Java is an instance of java.lang.String. A string in Clojure is the exact same thing.

(class "caterpillar")
;; -> java.lang.String

We can transform this string to uppercase using the String’s method toUpperCase.

The way to call toUpperCase in Java, would be to call it on the string itself with a dot:

String cString = new String("caterpillar");
cString.toUpperCase();

We do this in Clojure by using a dot followed by the object and the object’s method that we wish to invoke:

(. "caterpillar" toUpperCase)
;; -> "CATERPILLAR"

There is also a shorthand dot prefix way to do the same thing by using a dot followed by the object’s method that we wish to invoke:

(.toUpperCase "caterpillar")
;; -> "CATERPILLAR"

If the Java method takes arguments, they are included after the object. For example, if we wanted to find the index of the substring “pillar” using the string’s indexOf method (which takes a character as a parameter), in Java we would do something like:

String c1String = new String("caterpillar");
String c2String = new String("pillar");
c1String.indexOf(c2);

In Clojure, the first argument is the string we want to call the method on, and the second is the argument:

(.indexOf "caterpillar" "pillar")
;; -> 5

We can create instances of Java objects with new:

(new String "Hi!!")
;; -> "Hi!!"

Another way to create an instance of a Java class from Clojure is to use a shorthand form for creation by using a dot right after the class name:

(String. "Hi!!")
;; -> "Hi!!"

What if we wanted to work with a Java object that we needed to import? Let’s take an example of needing to reach in and do some interop networking with Java. In particular, we need to work with a java.net.InetAddress that represents an IP. How do we create one? The first thing we need to do is import the Java class. We can do this by using :import in the namespace with the package name and the class that we wish to import:

(ns caterpillar.network
  (:import (java.net InetAddress)))

We can now create an instance of InetAddress. The way to create a new InetAddress in Java is to use a static method called getByName that takes a string of the hostname and resolves the matching IP address. To execute static methods on Java classes from Clojure, we use a forward slash:

(InetAddress/getByName "localhost")
;; -> #<Inet4Address localhost/127.0.0.1>

Now we have a Java object that we act on and get a property off of with the dot notation:

(.getHostName (InetAddress/getByName "localhost"))
;; -> "localhost"

We can also use Java classes without importing them by using their fully qualified names:

(java.net.InetAddress/getByName "localhost")
;; -> #<Inet4Address localhost/127.0.0.1>

There is also a doto macro, which allows us to take a Java object and then act on it in succession with a list of operations. This is useful if we have a Java object that we need to mutate in a series of steps. We can show this with Java’s StringBuffer object, which is a class that helps build strings. It takes an initial string as an argument. Then, if we call the method append with a string, it will change the object by adding that string to it:

(def sb (doto (StringBuffer. "Who ")
         (.append "are ")
         (.append "you?")))

(.toString sb)
;; -> "Who are you?"

This doto syntax is much nicer to read than the alternative nested version:

(def sb
  (.append
   (.append
    (StringBuffer. "Who ")
    "are ")
   "you?"))

(.toString sb)
;; -> "Who are you?"

Table 4-1 shows the code equivalents of using interop with Java compared to Clojure.

Table 4-1. Interop compared with Java
Java Clojure

"caterpillar".toUpperCase();

(.toUpperCase "caterpillar")

"caterpillar".indexOf("pillar");

(.indexOf "caterpillar" "pillar")

new String("Hi!!");

(new String "Hi!!")

new String("Hi!!");

(String. "Hi!!")

InetAddress.getByName("localhost");

(InetAddress/getByName "localhost")

host.getHostName();

(.getHostName host))

The ability to use Java classes and libraries in such an easy way is a real advantage in Clojure. As the popularity of Clojure has spread, there are now more Clojure libraries than ever to choose from. For example, you can use Java classes to generate a universally unique identifier (UUID). Because they are very common in generating IDs for orders, customers, or images in computer programs, here is how you can use a UUID in your Clojure program:

(import 'java.util.UUID)  1
(UUID/randomUUID)  2
;; -> #uuid "f9877259-2cc1-4e5a-8c6f-8b51499cb9f8"
1

Importing the Java class for UUID.

2

Calling the method on the Java class to give us a unique and random UUID.

You now have the power to interact with Java’s classes.

It is time to look at another way that Java’s classes help out Clojure: polymorphism. We will take a closer look at the different ways Clojure achieves polymorphism next.

Practical Polymorphism

In an object-oriented language like Java, there are a large amount of types for every situation. Clojure takes another approach. It has a small amount of types and many different functions for them. However, being pragmatic, Clojure realizes that polymorphism is flexible and useful for some situations. Let’s take a look at a few ways that Clojure can flex its polymorphic muscles.

If we wanted to have a function that would behave differently based on the kind of input we had, we could use a case like statement. This example uses a function called cond that behaves differently depending on whether the argument is a keyword, string, or number, and returns the caterpillar’s questions to Alice:

(defn who-are-you [input]
  (cond
    (= java.lang.String (class input)) "String - Who are you?"   1
    (= clojure.lang.Keyword (class input)) "Keyword - Who are you?" 2
    (= java.lang.Long (class input)) "Number - Who are you?"))  3

(who-are-you :alice)   4
;; -> "Keyword - Who are you?"

(who-are-you "alice") 5
;; -> "String - Who are you?"

(who-are-you 123) 6
;; -> "Number - Who are you?"

(who-are-you true) 7
;; -> nil
1

The class input is compared, and if it is a string it will return "String - Who are you?"

2

If it is a keyword, it will return "Keyword - Who are you?"

3

If it is a number (class of Long), it will return "Number - who are you?"

4

When called with a keyword, returns the clause that matched the keyword class.

5

When called with a string, returns the clause that matched the string class.

6

When called with a number, returns the clause that matched the number class.

7

When called with a boolean, returns nil because there is no matching cond clause.

We can express this with polymorphism in Clojure with multimethods. We first need to define the multimethod and a function that specifies how it is going to dispatch; that is, how it is going to decide which of the following methods to use. In the case of our who-are-you function, the dispatch is going to be on the class of the input:

(defmulti who-are-you class) 1

(defmethod who-are-you java.lang.String [input] 2
  (str "String - who are you? " input))

(defmethod who-are-you clojure.lang.Keyword [input] 3
  (str "Keyword - who are you? " input))

(defmethod who-are-you java.lang.Long [input] 4
  (str "Number - who are you? " input))

(who-are-you :alice) 5
;; -> "Keyword - who are you? :alice"

(who-are-you "Alice") 6
;; -> "String - who are you? Alice"

(who-are-you 123) 7
;; -> "Number - who are you? 123"

(who-are-you true) 8
;; -> IllegalArgumentException No method in multimethod
;;'who-are-you' for dispatch value: class java.lang.Boolean
1

We are declaring that the who-are-you function is going to be a multimethod with a single argument. The function that will be used for choosing what method to use is the class function. This class dispatch function takes only a single argument.

2

Using defmethod, we say that if the class of the input is a String, then we will pass the original value of the input to a str function that will construct the "String - who are you .." return value.

3

We do a similar defmethod dispatching on the Keyword class.

4

And another defmethod dispatching on the Long class.

5

When we call the who-are-you function with a keyword, it not only uses the method defined for keywords, it also returns the value of the :alice input in the return string.

6

Calling with a string results in the function defined for the string class along with the “Alice” value in the return string.

7

Calling with a number also shows that the function defined for the Long class was used along with the number 123.

8

Calling with a boolean throws an error because it couldn’t find a matching dispatch method.

We could also provide a default dispatch method using the :default keyword, so if we don’t have a matching one it will use that instead of throwing an exception:

(defmethod who-are-you :default [input]
  (str "I don't know - who are you? " input))

(who-are-you true)
;; -> "I don't know - who are you? true"

In the previous example, the dispatch function is called first, which is the class of the input. Then, using that value, it decides what method to use.

Really, any function can be given to dispatch on. So, we can even inspect the value of a map as input. What if we wanted to have a multimethod to control the conversation of the caterpillar based on the value of Alice’s question?

In this example, we are going to create a multimethod that is dispatched on a function of her height, so that she knows which side of the mushroom to eat from.

First, we declare that the function named eat-mushroom is going to be a multimethod with def-multi. This time, instead of using the class function, we are going to define our own. It is a function that takes a one parameter, height. If the height is less than 3, then the :grow keyword will be returned; otherwise, the :shrink keyword will be returned:

(defmulti eat-mushroom (fn [height]
                          (if (< height 3)
                            :grow
                            :shrink)))

The :grow and :shrink keywords that we are choosing to dispatch on now need defmethods for each of them. For the :grow keyword, we will simply return a helpful string that tells the user to eat the right side to grow:

(defmethod eat-mushroom :grow [_]
  "Eat the right side to grow.")

Then the :shrink keyword will do something similar, only it will return a helpful string to eat the other side of the mushroom:

(defmethod eat-mushroom :shrink [_]
  "Eat the left side to shrink.")
Note

You will notice that we are using an underscore instead of using a name for the parameter in the defmethods. This is an idiomatic way to say that we don’t care about the value of the input here—we are not going to use it, and effectively ignore it.

When we try call the eat-mushroom function with a small height, it will tell us the hint to grow:

(eat-mushroom 1)
;; -> "Eat the right side to grow."

Likewise, when we call it will a big height, it will tell us the hint to shrink.

(eat-mushroom 9)
;; -> "Eat the left side to shrink."

Another way to use polymorphism in Clojure is to use protocols. Where multi-methods are great using polymorphism on one function, sometimes protocols can handle polymorphism elegantly with groups of functions. Let’s take a look at this with the eat-mushroom example using a String, Keyword, and a Long. First, we need to define the protocol:

(defprotocol BigMushroom
  (eat-mushroom [this]))

Next, we implement the protocol for all our types at once using extend-protocol. The parameter this is the thing that we are going to perform the function on:

(extend-protocol BigMushroom
  java.lang.String
  (eat-mushroom [this]
    (str (.toUpperCase this) " mmmm tasty!"))

  clojure.lang.Keyword
  (eat-mushroom [this]
    (case this
      :grow "Eat the right side!"
      :shrink "Eat the left side!"))

  java.lang.Long
  (eat-mushroom [this]
    (if (< this 3)
      "Eat the right side to grow"
      "Eat the left side to shrink")))

Now, we can call the function with the data types quite naturally:

(eat-mushroom  "Big Mushroom")
;; -> "BIG MUSHROOM mmmm tasty!"

(eat-mushroom :grow)
;; -> "Eat the right side!"

(eat-mushroom 1)
;; -> "Eat the right side to grow"

We have been using protocols to add methods to existing data structure. However, what if we want to add our own?

Clojure’s answer to this is data types. There are two solutions depending on what you are looking for. If you need structured data, the answer is to use defrecord, which actually creates a class with a new type. The defrecord form defines the fields that the class will hold. To demonstrate, we will make a defrecord to describe the mushroom that the caterpillar was sitting on when Alice met him. It had a color and a height:

(defrecord Mushroom [color height])
;; -> caterpillar.network.Mushroom

Now we can create a new mushroom object with a dot notation:

(def regular-mushroom (Mushroom. "white and blue polka dots" "2 inches"))
;; -> #'caterpillar.network/regular-mushroom

(class regular-mushroom)
;; -> caterpillar.network.Mushroom

Notice that the class type that was produced was the same as the one defined by defrecord. We can get the values with the dot-dash that is preferred over the dot-prefix form for accessing fields:

(.-color regular-mushroom)
;; -> "white and blue polka dots"

(.-height regular-mushroom)
;; -> "2 inches"

We can combine the structured data and type that defrecord gives us with protocols to implement interfaces. The mushroom that Alice encountered in Wonderland was special. If she ate from one side of the mushroom it made her grow big, and the other side made her grow small. Let’s define a protocol for a mushroom to be edible. Of course, it will work differently on different types of mushrooms. The protocol will be called Edible and it will consist of two functions: one called bite-right-side and one called bite-left-side. Each of these functions takes this as an argument, which is the record itself that we will call it with later:

(defprotocol Edible
  (bite-right-side [this])
  (bite-left-side [this]))

Now that we have a protocol defined, we can start having records that implement it. The type of record that we will make is a WonderlandMushroom:

(defrecord WonderlandMushroom [color height] 1
  Edible 2
  (bite-right-side [this]  3
    (str "The " color " bite makes you grow bigger"))
  (bite-left-side [this]  4
    (str "The " color " bite makes you grow smaller")))
1

Creates a WonderlandMushroom record that takes arguments that set the color and height.

2

Implements the Edible protocol.

3

Defines the implementation for the bite-right-side function.

4

Defines the implementation for the bite-left-side function.

Next, we define a record for a RegularMushroom. It is very similar to the WonderlandMushroom. It has the same constructor, and implements the Edible protocol. The main difference is in what the functions do. The bites of the mushroom don’t make you grow bigger or smaller. They just taste bad:

(defrecord RegularMushroom [color height]
  Edible
  (bite-right-side [this]
    (str "The " color " bite tastes bad"))
  (bite-left-side [this]
    (str "The " color " bite tastes bad too")))

Finally, we can construct our mushrooms with the record dot syntax:

(def alice-mushroom (WonderlandMushroom. "blue dots" "3 inches"))
(def reg-mushroom (RegularMushroom. "brown" "1 inches"))

When we take bites from the WonderlandMushroom, they give us the growing messages:

(bite-right-side alice-mushroom)
;; -> "The blue dots bite makes you grow bigger"

(bite-left-side alice-mushroom)
;; -> "The blue dots bite makes you grow smaller"

And when we take bites from the RegularMushroom, they taste bad:

(bite-right-side reg-mushroom)
;; -> "The brown bite tastes bad"

(bite-left-side reg-mushroom)
;; -> "The brown bite tastes bad too"

We have gone through a fun example with protocols and Alice in Wonderland. But we will stop for a moment to talk about when to use protocols in a practical setting.

A real-world example of protocols is implementing different types of persistence. It is common in a business setting to want to write information to a data source. The information that we write stays the same, but we are writing it to different types of data sources. We could have one defrecord type persist the result to a database and another could persist the result to an Amazon S3 bucket. We can easily adapt the same technique we used with mushrooms to store information.

In the previous example, we were using records that held structured data values. Sometimes we don’t really care about the structure or the map lookup features provided by defrecord, we just need an object with a type to save memory. In this case, we should reach for deftype. We can show this using the mushroom example, except this time, we don’t care what color the mushroom is, or how tall it is.

The protocol itself doesn’t change:

(defprotocol Edible
  (bite-right-side [this])
  (bite-left-side [this]))

The difference is that instead of using defrecord, we are now going to use deftype:

(deftype WonderlandMushroom [] 1
  Edible 2
  (bite-right-side [this] 3
    (str "The bite makes you grow bigger"))
  (bite-left-side [this] 4
    (str "The bite makes you grow smaller")))
1

Use deftype to define a WonderlandMushroom with no arguments.

2

It implements the Edible protocol.

3

The function for bite-right-side is simply a string telling you that it makes you bigger.

4

The function for bite-left-side likewise tells you that it will make you smaller.

The RegularMushroom looks the same as the WonderlandMushroom (with less magic, of course):

(deftype RegularMushroom []
  Edible
  (bite-right-side [this]
    (str "The bite tastes bad"))
  (bite-left-side [this]
    (str "The bite tastes bad too")))

We construct the mushrooms the same way as before with the dot notation:

(def alice-mushroom (WonderlandMushroom.))
(def reg-mushroom (RegularMushroom.))

Tasting the mushrooms gives the growing response for the WonderlandMushroom and the taste bad response for the RegularMushroom:

(bite-right-side alice-mushroom)
;; -> "The bite makes you grow bigger"

(bite-left-side alice-mushroom)
;; -> "The bite makes you grow smaller"

(bite-right-side reg-mushroom)
;; -> "The bite tastes bad"

(bite-left-side reg-mushroom)
;; -> "The bite tastes bad too"

The main difference between using protocols with defrecord and deftype is how you want your data organized. If you want structured data, choose defrecord. Otherwise, use deftype. Why? Because with records, you get type-based dispatch and you can still manipulate your data like maps (which is great for reuse). Sometimes, when this structured data isn’t needed, you can use deftype to avoid paying for the overhead for something you don’t want.

Clojure protocols and data types are powerful solutions when you need them, but beware! Many people who come from an object-oriented background tend to reach for them and use them just because they are similar to how they are used to modeling and thinking about code.

Caution

Think before you use protocols.

In the example we just did using protocols, we could have actually used other ways to get the same result. Instead of using a protocol, we could have used a simple map to distinguish what kind of mushroom it was.

We could define the bite-right-side function to take a mushroom as an argument. This argument would be a map containing a key of :type. If the :type key value is equal to the string "wonderland", then we could know that it was a special mushroom that could make you grow bigger. Otherwise, it would just be considered a regular mushroom:

(defn bite-right-side [mushroom] 1
  (if (= (:type mushroom) "wonderland")
    "The bite makes you grow bigger"
    "The bite tastes bad"))
1

The mushroom argument is a map with a key :type in it.

We could then define a similar function for the left side:

(defn bite-left-side [mushroom]
  (if (= (:type mushroom) "wonderland")
    "The bite makes you grow smaller"
    "The bite tastes bad too"))

When we bite into the wonderland mushroom, with the map key :type set to "wonderland", it will give us the growing messages:

(bite-right-side {:type "wonderland"})
;; -> "The bite makes you grow bigger"

(bite-left-side {:type "wonderland"})
;; -> "The bite makes you grow smaller"

And of course, when we bite into the regular mushroom, it tastes bad:

(bite-right-side {:type "regular"})
;; -> "The bite tastes bad"

(bite-left-side {:type "regular"})
;; -> "The bite tastes bad too"

As you can see, there are multiple ways to get functions to behave differently based on values and types.

Protocols should be used sparingly. In most situations, a pure function or multimethod can be used instead. A nice thing about Clojure is that it is easy to move from just maps to records when you need to. This allows you to delay the decision of whether or not to use protocols.

You can now handle real-world state and concurrency with atoms, refs, and agents. You can also handle polymorphism in a practical way. The tools to conquer stuctured data, types, and interfaces where pure functional approaches don’t work are in your hands. You have all the skills you need to start creating your own Clojure projects and explore the ecosystem in the next chapter.

Get Living Clojure now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.