I don't like Java. If care to find out why you can search my blog, but it's no secret Java is not exactly at the top of my list of favorite programming languages. But I am not stupid enough to think that Java is about to go away. And I have nothing against the JVM, just the primary language that runs on it.
Thanks to the JVM, just because I have to work with Java code does not mean I have to write Java code. Thanks to various languages being ported to the JVM there are now multiple options for working with Java code in languages other than Java:
- Jython - port of Python.
- JRuby - port of Ruby.
- Rhino - port of ECMAScript.
- Clojure - Lisp dialect.
- Groovy - Scripting language for the JVM.
- Scala - object-oriented, functional language for the JVM.
By targeting the JVM all the above languages can call Java code in order to be relevant in a Java-heavy world. But just because they can easily consume Java code does not mean that the reverse is true. What I am looking for is not just a way to call into Java, but to call other languages from Java.
To better explain this, take the following three files. First, I have Spam.java:
I have another class that implements a subclass, BaconSpam.java:
public class Spam {
public String serves() {
return "spam";
}
}
public class BaconSpam extends Spam {And finally, the class that runs the show, Waitress.java:
@Override
public String serves() {
return "bacon " + super.serves();
}
}
public class Waitress {What I am after is a language to rewrite BaconSpam.java in another JVM language such that I don't touch Spam.java. I also want the changes to Waitress.java to be minimal or non-existent while still having to store an instance of the class and the value returned by serves() to show how objects would be stored and used in a long-running Java application (i.e. no cheating by inlining some call in the println() call to make the example a little tougher and more "real world").
public static void main(String[] args) {
BaconSpam menu = new BaconSpam();
String food = menu.serves();
System.out.println("We serve " + food + "!");
}
}
Jython
Thanks to the Jython guys I was pointed to a Jython Monthly article from October 2006 that explains how best to go about accessing Jython code from Java.
To start I wrote BaconSpam in Python:
import Spam
class BaconSpam(Spam):
def serves(self):
return " ".join(["bacon", Spam.serves(self)])
While that was rather simple, there is still the issue of getting an instance of the class. Because Jython dynamically interprets Python code you can't simply drop in BaconSpam.py and have it work with Waitress.java. You need to create an instance of org.python.util.PythonInterpreter and interface with it to get at the Python code:
import org.python.core.__builtin__;
import org.python.core.PyObject;
public class Waitress {
public static void main(String[] args) {
PyObject BaconSpam = __builtin__.__import__("BaconSpam").__getattr__("BaconSpam");
Spam menu = (Spam)BaconSpam.__call__().__tojava__(Spam.class);
String food = menu.serves();
System.out.println("We serve " + food + "!");
}
}
Because Jython dynamically loads Python code, Waitress.java has to be modified. Luckily a large chunk of the code is boilerplate that can be extracted out into a factory class to help simplify things.
And just a warning for anyone wanting to run the above code: for some odd reason the above code only worked for me with Jython 2.5b3, not 2.2.1.
JRuby
Writing the BaconSpam subclass was simple:
class BaconSpam < Spam
def serves
"bacon " + super
end
end
But as of right now you need to use JSR 223 to interface with the class which is worse than the Jython approach, so I am not going to go through the steps here.
But Charles Nutter has blogged about adding signature support to JRuby's in-dev compiler2. It looks like as soon as inheritance is handled properly in compiler2 that JRuby will be in the same position at Groovy and Scala for ease of integration (see below for details on those two languages).
Rhino
I don't like JavaScript, so I am skipping Rhino. =) But it's another JSR 223 approach.
Clojure
I actually didn't get Clojure to work. I tried to follow the gen-class example from the Clojure wikibooks, but ran into several issues that included having to manually execute compilation for the Eclipse plug-in to even get an error message and putting the code in a package to get Clojure to not assume I was working off of java.lang when inheriting from Spam.java.
(ns pkg.BaconSpam
(:gen-class
:extends pkg.Spam
:exposes {serves servesSuper}))
(defn -serves [this]
(str "bacon " (.servesSuper this))
)
With an error of "java.lang.IllegalArgumentException: Don't know how to create ISeq from: Symbol", I just stopped trying to make this work. I assume Waitress.java will need to be changed beyond just being put in a package anyway in order to deal with Clojure's dynamic typing.
Groovy
Writing the Groovy version of BaconSpam was very easy thanks to the language having been designed for the JVM from the outset. The only trick was that serves() needed to have the return type specified for the method instead of being dynamic:
public class BaconSpam extends Spam {
// Using 'def' makes return value dynamic.
String serves() {
return "bacon " + super.serves()
}
}
With the typed method there is no need for modifying Waitress.java. Groovy basically ends up looking like Java with some syntax removed.
One issue that did come up with writing the Groovy example was that the Eclipse plug-in is in some bad shape; I couldn't get it to run the project. This drove me to download and use NetBeans since it has Groovy support included. That was a much better experience since it actually worked.
Oh, and the docs suck. Took way too long to figure out how the darn language is even structured. Just had to read various examples to figure things out.
Scala
Much like Groovy, Scala was easy to use to rewrite BaconSpam.
class BaconSpam extends Spam {
override def serves(): String =
return "bacon " + super.serves()
}
And just like Groovy there was no need to change Waitress.java in order to interact with the class. But unlike Groovy the Eclipse plug-in for Scala actually allowed me to execute the application. Plus the docs are better so I didn't have to go digging around to figure out what I needed to do.