If you know anything about me, you probably know that I'm a big fan of Groovy. But why? I've never really addressed that question head on, I don't think, so I'm going to do that here (prompted by a comment from David McGuigan on my CFGroovy 1.0 post).
First, Groovy is a dynamic language for the JVM, in much the same vein as CFML, Jython or JRuby. By "dynamic" I mean that things get figured out at runtime. Contrast this with a static language like Java, where everything is wired together at compile time (aside from reflection). For example, in order to reference a variable in Java, the variable has to be declared in the source and available at compile time. With a dynamic language, the variable only has to be there when you reference it. It doesn't have to exist before then.
Groovy is also an "essential" language (again like CFML, Python, Ruby) in that "ceremonious" constructs are minimized. In Java, you have to do a lot of boilerplate/ceremonious coding (handling checked exceptions, types, manual decoration, etc.). With an essential language that is minimized as much as possible. Here's a simple example of reading a file with Groovy:
text = new File("/path/to/file.txt").text println(text)
and in CFML:
<cfset text = fileRead("/path/to/file.txt") /> <cfoutput>#text#</cfoutput>
and here it is in Java:
import java.util.Scanner; String text = null; Scanner scanner = new Scanner(new File("/path/to/file.txt")); try { text = scanner.useDelimiter("\\Z").next(); } finally { scanner.close() } System.out.println(text); import java.io.BufferedReader; import java.io.FileReader; import java.io.IOException; public class JavaTest { public static void main(String[] args) { BufferedReader r = null; try { r = new BufferedReader(new FileReader("/path/to/file.txt")); StringBuffer sb = new StringBuffer(); while (r.ready()) { sb.append(r.readLine()).append("\n"); } String text = sb.toString(); System.out.println(text); } catch (IOException ioe) { // well crap System.err.println("got an IOException: " + ioe); if (r != null) { try { r.close(); } catch (IOException ioe2) { // oh well } } } } }
Ick.
With the Scanner class (which I was not aware of until a commenter mentioned it) the Java example isn't terribly worse than the Groovy and CFML examples, so I retract my previous "ick" statement. The Java is certainly less direct, however.
So we want to use Groovy or CFML instead of Java for this sort of thing, but why Groovy in particular? Unlike CFML (or Jython, JRuby, etc.) Groovy is designed as something of a language extension to Java, rather than a totally separate language in it's own right. To put that another way, Groovy builds on not just the JVM, but also on the Java language itself to a large degree. It makes many things enormously simpler compared to Java, but using a familiar syntax. In fact, nearly 100% of Java syntax is also valid Groovy.
That buys us several really nice features. First, Groovy is easy to learn if you've done any Java. Second, it means that the language constructs map back to Java constructs almost directly, which means accessing Java code from Groovy is a snap. Finally, it means you can use Groovy to create "Java" constructs, and since Groovy is dynamic, you can do it on the fly. If you've ever tried to leverage Java libraries from CFML, you know the pains of createObject, native arrays, and null. Here's an example of creating a URL[] (a Java array of URLs) from comma-delimited string of filenames in CFML:
<cfset urlClazz = createObject("java", "java.net.URL").init("http://barneyb.com/").getClass() />
<cfset Array = createObject("java", "java.lang.reflect.Array") />
<cfset path = "/path/to/lib.jar,/path/to/otherlib.jar" />
<cfset urlArray = Array.newInstance(urlClazz, listLen(path)) />
<cfset i = 0 />
<cfloop list="#path#" index="item">
<cfset Array.set(urlArray, i, createObject("java", "java.io.File").init(item).toURL()) />
<cfset i = i + 1 />
</cfloop>
and in Groovy:
path = "/path/to/lib.jar,/path/to/otherlib.jar".tokenize(",")
urlArray = new URL[path.size]
path.eachWithIndex { it, i ->
urlArray[i] = new File(it).toURL()
}
As you can see, Groovy's "closeness" to Java makes dealing with Java constructs enormously easier. Of course, if you're not using Java libraries, this benefit is of minimal benefit. The Groovy example also illustrates a closure and the eachWithIndex iterator. This is where Groovy really shines. You can write incredibly concise code without sacrificing readability.
Groovy also allows you to create classes on the fly in your scripts, something that CFML has no way of providing. Need a Comparator? Or perhaps a Runnable? You can create those inline, no separate files, no compilation, nothing. As an example, let's say I have a List of Maps (or an array of structs in CFML) and I want to sort them by the "letter" Map/struct key. Here's some CFML to do it:
<!--- myArray is an array of structs, each with a "letter" key ---> <cfset keys = "" /> <cfloop from="1" to="#arrayLen(myArray)#" index="i"> <cfset keys = listAppend(keys, myArray[i].letter & "~" & i) /> </cfloop> <cfset newArray = [] /> <cfloop list="#listSort(keys, 'textNoCase')#" index="key"> <cfset arrayAppend(newArray, myArray[listLast(key, "~")]) /> </cfloop> <cfset myArray = newArray />
CFML has no way of sorting an array of complex objects, so we have to fake it by building a collection of simple objects (a list of Strings, in this case), sorting that, and then reversing it back to the complex objects. This also has a very undesirable (though non-obvious) side effect: it changes the myArray reference, not just the myArray object. With CFML's pass-by-value semantic for arrays it's not as big a deal as it might otherwise be, but still a nasty consequence that can yield some really insidious bugs.  You can beat it by replacing the last line with this:
<cfset myArray.clear() /> <cfset myArray.addAll(newArray) />
This keeps the myArray reference intact, but requires a bit of knowledge about the Java Collections framework.
Here's the same example in Groovy:
// myArray is a List of Maps, each with a "letter" key
Collections.sort(myArray, {o1, o2 ->
o1.letter.compareTo(o2.letter)
} as Comparator)
Here I'm using a closure (the part in blue) as a Comparator instance and using the Collections.sort method. Without a comparator, there's no way to use this method, which is why the CFML has do the whole thing manually. The example requires a little knowledge of the Java Collections framework, but the readability and maintainability of the code is enormously better, so it'll cover the cost of learning very quickly. And as you saw, in order to get the CFML code to actually do what you want (sort the array, not create a new array in sorted order) you end up having know about the Java Collections framework anyway.
Finally, Groovy also provides a rich metaprogramming environment. I'm not going to show any code, but simply put, metaprogramming is a way of having code effect the program while it's running. To put that another way, programming effects data, metaprogramming effects the program itself. For example, you can add new methods to objects (or whole classes of objects) at runtime. This is the acme for dynamic languages – being so dynamic the code itself is mutable at runtime.
What about downsides?
First, Groovy is a new language to learn; no getting around that. You have to learn new syntax and new idioms, and keep them sorted from other languages' syntax and idioms.
Second, if you have a significant investment in another language, leveraging Groovy requires a integration features. With Java it's simple – the Groovy compiler provides that. I struggled with Jython integration, though I'll admit I didn't spend much time on it. For CFML, I've invested significant effort in building CFGroovy to address this issue, but it still has limitations.
Third, Groovy's focus on dynamic execution and metaprogramming has performance implications for certain types of operations. If you're writing a mathematics library, for example, Groovy is a horrible choice. As such, unless you development needs are constrained to high level concepts, Groovy is probably not the best choice for a one-size-fits-all language. Groovy is not unique in this way, of course. CFML, Jython and JRuby all seem to have lower performance penalties, but at the expense of "dirtier" Java integration and less comprehensive dynamic capabilities. Different language / different focus.
In conclusion, Groovy is not a panacea. It's nothing more than a potential tool to have in your developer toolbox. However, when integrated into an existing JVM-based environment, or as a foundation language for new development, it can be an incredibly powerful tool.
Nice post!
Spectacularly informative post. Exactly what I needed. Even included a microdiscussion of performance comparisons! Mad, no, furious props.
David,
Glad it was useful. Regarding performance, I probably implied far more than I intended. If you're doing a math library, all of the dynamic languages are a bad choice. ;) Of the languages on the table, Java is the right choice for that job, even with the additional pain in development. For certain types of operations, I've found 2-3 orders of magnitude improvement switching from Groovy to Java. Yes, a thousand times faster. I haven't tested as much with other dynamic languages, but CFSCRIPT was 10-20% faster than Groovy for those same sorts of tasks. Compared to 10,000% for Java, however, it's a drop in the ocean.
Just a brilliant post. BarneyB is smart.
Is there a good site for looking up Groovy syntax / API? Maybe a… Groovy cheatsheet?
Thanks!
So you like terseness, huh? How about this, then:
urls = "/path/to/lib.jar,/path/to/otherlib.jar".tokenize(",").inject([]){ urls, url -> urls.add(new File(url).toURL()); return urls; }
Groovy is fun indeed ;)
Henry,
The main Groovy site's documentation page (http://groovy.codehaus.org/Documentation) has a number of good resources. It has "learn the language" stuff, as well as API docs, and a whole pile of examples.
Kevin,
That's just the same as a .collect call, no? I.e.:
urls = "/path/to/lib.jar,/path/to/otherlib.jar".tokenize(",").collect{new File(it).toURL()}
URLClassLoader requires URL[] not a List and that's the part I couldn't figure out how to do inline.
@barneyb, awesome, thanks.
Thanks for the post,
I havnt been following Groovy all that much to be honest, should probably start.
In your experience, what would the barrier to entry be for someone who is a PHP developer for instance?
All your saying is "scripting in java would be nice". "And if it could leverage standard APIs great!"
Which it obviously can. So what.
Scripting in Java is out there besides groovy without creating a new for another language.
And besides, groovy is slower than all the other except maybe jruby.
So why care?
Comparisons like these are dishonest. As the Scala guys say: you could consider the Scala jar to be 'just another' Java library. In the same sense, Groovy is a library and behind that single line of Groovy is a *lot* of code. If you are going to compare Groovy to Java, a fair comparison would at least involve the 'regular' Java libraries, like Apache Commons IOUtils to read a file.
The file printout sample that you did on java is not true, too many catchs. What about the groovy exception management ? It shows you're devoted to groovy.
But anyways, after my critic I will tell you I will start with groovy as a "on the fly" testing tool for my apps. I really want to learn it. Seems a really easy to learn scripting.
thanks for the article.
Regards
bob, Ivo, and Rodrigo,
First, my readership is primarily CFML and Flex developers, not Java developers. So it's not about "Groovy over Java", it's about "Java is a PITA but Grovoy is a nice halfway". I'm not going to get into a "Java is a PITA" argument, but I think we can all agree that coming from a a web scripting language (e.g., CFML), any sort of compilation and restarting is a significant downside. Leveraging some of the power of Java without paying the full "cost" (via scripting language X) is a huge win.
Like all "check this out" posts, there is a certain amount of over-promoting the good and glossing over the bad. That said, none of your critiques are off base. Groovy is nothing more than Java library for scripting on the JVM, much akin to any other libraries that adds feature X, Y or Z.
Sean,
The biggest barrier to entry coming from PHP is probably JVM integration. I've never used the Java integration stuff for PHP, so I don't know how good it is. After that it's really just learning the new syntax and idioms and how to best leverage each language for it's strengths. Of course, you can use Groovy as a standalone scripting language (think CLI-style PHP) as well, which can be quite handy.
Excellent post! Thanks for passing on all this info. Can't wait to try the latest CFGroovy, haven't had time yet.
Your java example is a bit unfair, as I believe this would work equally:
String file = new Scanner( new File( "/path/to/file.txt" ) ).useDelimiter( "\\Z" ).next() ;
agree with the Scanner sample
Tim,
Very nice! I had no idea that class existed.
yeah, I think ignorance was the main misunderstanding here. But anyways, Groovy seems promising and must take his place as py does.
Java code which uses Scanner, now it seems to throw unchecked exception on error rather than checked exceptions which is what Java programmers expect. Who closes the file? Does scanner does it? Why don't we write better groovy code using RAII, which is so better compared to java's finally