This is Part Two of a series of articles on Java.next. In Part Two, I will look at how Java.next languages interoperate with Java.
Java interop is trivial in all of the Java.next languages. We have Java itself to thank for this–the Java Virtual Machine Specification makes it easy for other languages to reflect against and call Java code.
As a first example, consider calling into the Java Swing API to create an application [1] that has
For starters, here is the application in plain old Java:
Below, I will present the same Swing application, ported to the Java.next languages. Please take note of two things about these examples:
Groovy is the Java.next language that looks most like Java. Here is the same example in Groovy:
If you compare this to the Java example, it is almost the same code, minus a bunch of unnecessary ceremony. The Groovy version lets us omit:
get
and set
for property accessThe most important benefit, however, comes in the action listener. The Groovy version sports
it.actionCommand
(inside ${})For a more idiomatic approach to Swing in Groovy, see the Groovy SwingBuilder project.
Since this post is about Java interop I will state the obvious: From Groovy, Java interop is entirely trivial.
Next, let's look at the Scala version:
The Scala version offers many of the same advantages over Java that the Groovy version provided:
We also see a few items unique to Scala:
_
, not *
. In Scala, *
is a valid identifier. (Scala's punctuation-friendly identifiers will be a big advantage later when I am writing DSLs.)object
instead of a class
.Again, the most important differences are in the action listener. Like Groovy, Scala lets us skip the anonymous inner class ritual, and simply pass a function:
That looks great, except I cheated a little. Scala's implementation of strong typing won't automatically coerce a function into an ActionListener
, so the above code won't compile out of the box. Fortunately, Scala's implicit conversions let us have our cake and eat it too: strong typing plus much of the syntactic convenience of a looser type system. All we have to do is tell Scala the the conversion is legal:
With this one-time setup in place, we can now pass a function where an ActionListener
is expected.
There seem to be several projects to wrap Swing in more idiomatic Scala. Using one of these libraries you should be able to get a syntax cleaner than the sample code here. See ScalaGUI for one example.
From Scala, Java interop is trivial.
Let's see how JRuby fares:
If you compare this to the earlier Groovy example, you will see almost exactly the same feature set:
get
or set
)evt.getActionCommand
(the stuff inside #{})The action listener callback is simplified in a fashion similar to the Groovy example. Ruby automatically generates the ActionListener
from a block:
In the JRuby example I used Ruby conventions for method names, even on Java objects:
Java programmers expect camel case. As a convenience, JRuby supports both naming conventions:
Ruby's flexibility has encouraged a lot of experimentation with alternate syntaxes for Java interop. See JRUBY-903 for some of the history. For a more idiomatic approach to Swing in JRuby, see the Profligacy project.
From JRuby, Java interop is trivial.
Here is the Clojure version:
Because Clojure is a Lisp, the syntax is radically different from the others. This deserves hours of discussion, or none. Since my focus here is on Java interop, I am going to save The Great Parenthesis Debate for a later entry in this series. For now, let us suspend judgment on syntax, and focus exclusively on the Java interop.
Importing Java classes is easy. import
takes a list. The first element of the list is a package, and the remaining elements are classes to add to the current namespace. Note that this allows the import of multiple classes in a single line.
Creating a Java instance is easy. Use the (class. &args)
form.
There are multiple ways to call methods on a Java class. If you want to call a single method, you can use the (.methodName obj &args)
form. For static calls, you can also use the (class/method &args)
form:
Sometimes you want to chain multiple calls together. Where in Java you would say x.y().z()
, in Clojure you can use the (.. x (y) (z))
form.
The last three method calls in our example are all on the same frame
object. With Clojure's doto
form, you can perform multiple operations on an object without having to repeat the object each time.
As with the other examples, the action listener is the most interesting part. In Clojure, proxy
will dynamically create a Java instance [2], allowing you to implement interfaces and methods as needed.
As with JRuby, this solution is more general, and requires more syntax, than the Groovy approach. Also as with JRuby, you can easily roll your own syntax.
From Clojure, Java interop is trivial.
The interop story in Java.next is almost boring: It Just Works. So to spice things up a little, here is an exercise in rolling your own constructs, inspired by the examples above. Consider Clojure's import
, which can import multiple Java classes in a single line of code.
Why can't this be even more general? Try your hand at writing a custom import function in one of the Java.next languages. Some useful features might be
Let me know what you come up with, and I will link to it here.
In the examples above, I have demonstrated how all of the Java.next libraries can trivially interoperate with Java. Each of examples called the Swing library with fewer lines of code than the Java version. More importantly, the Java.next versions capture the essence of the program with less ceremony.
Seamless interoperation with Java should not be the primary yardstick when measuring Java.next languages, because they all get it right. There are complexities and corner cases beyond what I have shown here, in all of the Java.next languages. But I consider the Java interop problem to be basically solved.
In these first two articles, I have stayed fairly close to Java style while demonstrating Java.next language features. With that groundwork in place, it is time to start using idiomatic Java.next. In the next installment of the Java.next series, we will look at how the Java.next languages support Domain-Specific Languages.
proxy
creates classes as necessary behind the scenes. In Java.next, the dichotomy of class and object is not constantly center stage.doto
.