Chapter 3. Interacting with Java
We know a bit about how to organize our code and how that relates to packages in Java. Now, you surely need to use your old Java code and all the libraries you already know; Clojure encourages a new way to think about programming and it also allows you to use all the dependencies and code that you've already generated.
Clojure is a Java Virtual Machine (JVM) language and as such it is compatible with most Java dependencies and libraries out there; you should be able to use all the tools out there. You should also be able to use your Clojure programs with Java-only programs, this requires a bit of custom coding but in the end you can use Clojure in the right places of your project.
To be able to do this, we'll have to learn:
Using Maven dependencies
Using plain old Java classes from your Clojure code base
A bit more about the Clojure language, in particular the let
statements and destructuring
Creating a Java interface for your Clojure code
Using the Java interface...
Let's say that we want to write an image manipulation program; it is a very simple program that should be able to create thumbnails. Most of our codebase is in Clojure, so we want to write this in Clojure too.
There are a bunch of Java libraries meant to manipulate images, we decide to use imgscalr, which is very simple to use and it looks like it is available in Maven Central (http://search.maven.org/).
Let's create a new Leiningen project, as shown:
Now, we need to edit the project.clj
file in the thumbnails project:
You can add the imgscalr
dependency similar to the following code:
Clojure was designed to be a Hosted Language, which means that it can run in different environments or runtimes. One important philosophy aspect is that Clojure does not attempt to get in the way of your original host; this allows you to use your knowledge of the underlying platform to your advantage.
In this case, we are using the Java platform. Let's look at the basic interrupt syntax that we need to know.
There are two ways to create an object in Clojure; for example, let's have a look at how to create an instance of java.util.ArrayList
.
Here, we are using the new
special form, as you can see it receives a symbol (the name of the class java.util.ArrayList
) and in this case it is an integer.
The symbol java.util.ArrayList
represents the classname
and any Java class name will do here.
Next, you can actually pass any number of parameters (including 0
parameters). The next parameters are the parameters of the constructor...
Writing a simple image namespace
Let's now write some Clojure code and create a file in src/thumbnails/image.clj
.
Let's try to do this the Clojure way. First of all, write the namespace declaration and evaluate it:
Now open up a REPL and write the following code:
We now have an image instance and you can call all of the Java methods in the REPL. This is one of Clojure's core concepts, you can play with the REPL and check your code before really writing it and you can do it in an interactive way, as shown:
In the end, we want to stick
with the following contents:
Now that you have written your image processing code, it is a good time to write the tests.
Let's just check if we can generate a thumbnail. Create a new thumbnails.thumbnail-test
namespace, in the tests.
Remember, if you create the file, it must be named test/thumbnails/thumbnail_test.clj
.
Add the following contents to it:
Destructuring is a feature in Clojure that is not common in other lisps; the idea is to allow you to write more concise code in scenarios where code doesn't really add value (for example, getting the first element from a list or the second parameter from a function) and concentrating only on what is important to you.
In order to understand this better, let's see an example of why destructuring can help you:
What's wrong with the previous code? Nothing really, but you need to start thinking about what is v
, what the first value of v
is, what the nth function does, and at what index v
starts.
Instead we can do this:
Once you are used to destructuring, you will see that you don't need to think about how to get the elements you need. In this case, we directly access the first, second, and third elements from our vector and use the first and third out of the three elements. With good naming...
Exposing your code to Java
If you want to be able to use Clojure code from other JVM languages, in Clojure, there are a couple of ways in which you can do it:
You can generate new Java classes and use them as you normally would; it can implement some interface or extend from some other class
You can generate a proxy on the fly, this way you can implement a contract (in the form of a class or an interface) that some framework requires with little code and effort
You can use the clojure.java.api
package to call Clojure functions directly from Java
Let's have a look at how we can define a Java class.
Create a new namespace called thumbnails.image-java
and write the following code:
There are situations when you are interacting with Java libraries, where you must send an instance of a specific Java class to some method; writing a class isn't the best option, you should rather create an instance that conforms to a contract expected by some framework on the fly. We have two options to do this:
Proxy: It allows you to implement a Java interface or extend from some super class. In reality, it creates a new object that calls your Clojure functions when needed
Reify: Reify allows you to implement interfaces and Clojure protocols (we will see them later). It is not capable of extending classes. It is a better performant than the proxy and should be used whenever possible.
Let's look at a minimal example:
In this chapter, you have gained a lot of power from Clojure with a few new primitives.
As you can see, there are plenty of ways to interact with your current codebase; specifically, you can now:
Use Java code from Clojure
Use Clojure code from Java
Reuse Java frameworks by creating objects that adhere to their contracts
With all of our new tools in mind, we are ready to tackle more concepts and a little bit more complexity with collections and data structures.