Stay ahead with the world's most comprehensive technology and business learning platform.

With Safari, you learn the way you learn best. Get unlimited access to videos, live online training,
learning paths, books, tutorials, and more.

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:

StringcString=newString("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:

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.netInetAddress)))

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:

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:

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:

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?"(= clojure.lang.Keyword(class input))"Keyword - Who are you?"(= java.lang.Long(class input))"Number - Who are you?"))(who-are-you:alice);; -> "Keyword - Who are you?"(who-are-you"alice");; -> "String - Who are you?"(who-are-you123);; -> "Number - Who are you?"(who-are-youtrue);; -> nil

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

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

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

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

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

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

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:

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.

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.

We do a similar defmethod dispatching on the Keyword class.

And another defmethod dispatching on the Long class.

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.

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

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

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:

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 (< height3):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-mushroom1);; -> "Eat the right side to grow."Likewise, when wecallitwillabigheight, itwilltellusthehinttoshrink.(eat-mushroom9);; -> "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-protocolBigMushroomjava.lang.String(eat-mushroom[this](str (.toUpperCasethis)" mmmm tasty!"))clojure.lang.Keyword(eat-mushroom[this](casethis:grow"Eat the right side!":shrink"Eat the left side!"))java.lang.Long(eat-mushroom[this](if (< this3)"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-mushroom1);; -> "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:

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:

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

Implements the Edible protocol.

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

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:

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:

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:

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.

With Safari, you learn the way you learn best. Get unlimited access to videos, live online training,
learning paths, books, interactive tutorials, and more.