A Taste of Groovy
Abstract
JVM popularised the idea of platform independence. However Java was the only language available to the developers who wanted to take advantage of the benefits of JVM. Developer productivity on Java was a real challenge and this lead to the development of Groovy language. Groovy is a dynamic language targeted to run on JVM and provides productivity benefits to the developers by embracing simplicity and being dynamic. Groovy is also highly interoperable with Java. In this article, I will walk you through code examples that demonstrate how Groovy is the language you always wanted Java to be.
Installation
- Download the distribution zip file from http://groovy-lang.org/download.html
- Extract the zip file
- Append the
bin
directory toPATH
environment variable.
Note: If you are on a *nix system, I would recommend installing groovy though GVM (http://gvmtool.net/) (now SDKMAN), which would give you the ability to switch between versions. This is handy if you are working on multiple projects that are running on different versions of Groovy. Visit http://groovy-lang.org/download.html#gvm for instructions.
Hello World
Continuing the tradition, let's take a look at hello world program in Groovy stored in file 'Hello.groovy'.
println 'Hello Developers!'
To run the program, switch to the directory where 'Hello.groovy' is present and execute the command groovy Hello.groovy
. Pause for a moment to imagine the amount of Java code required to achieve the same result!
The groovy
command will compile Groovy code into JVM byte code and run it immediately, without storing the generated byte code in a .class file. You could use groovyc
command to compile Groovy code into class files. In this example, Hello.groovy would compile to Hello.class. Now you could run this using java
command, with the only addition of groovy-all jar being available in the class path.
[code ]$ java -cp $GROOVY_HOME/embeddable/groovy-all-2.3.7.jar:. Hello
Hello Developers!
When all you want is to run a sequence of instructions, Groovy doesn't force you to enclose them in a class and method explicitly. This makes Groovy a good candidate for scripting. If your script has to run on both Windows and *nix systems, you would still need only one script.
Groovy Beans
Let's take a look at a Groovy bean class.
class Person{
String firstName
String lastName
int age
}
Person me = new Person()
me.firstName = 'Naresha'
println me.firstName
If you are a Java developer, I am sure you would get angry at me for accessing the attributes directly, without going through getter/setter methods. Let me illustrate that I haven't violated the rule.
To get the list of methods available in compiled Person class, let me add the following line of code, before I create the Person object.
println Person.metaClass.methods*.name
Output:
[equals, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait, __$swapInit, getAge, getFirstName, getLastName, getMetaClass, getProperty, invokeMethod, setAge, setFirstName, setLastName, setMetaClass, setProperty]
Naresha
You could spot getters and setters for firstName, lastName and age!
Let's further modify our Person class by adding customized getter and setter for firstName.
class Person{
String firstName
String lastName
int age
public String getFirstName(){
println "Getter called"
return firstName
}
public void setFirstName(String value){
println "Setter called"
firstName = value
}
}
Person me = new Person()
me.firstName = 'Naresha'
println me.firstName
Output:
Setter called
Getter called
Naresha
That proves we are indeed invoking setter/getter, without accessing the fields directly. Groovy generates getters and setters automatically, helping to reduce noise in our code. Also you get syntactic sugar for accessing getter/setters, without having to invoke the methods explicitly.
Dynamic Groovy
Groovy is a dynamically typed language. You could use def
keyword instead of the actual types.
def number = 10
println number.class // class java.lang.Integer
number = '10'
println number.class // class java.lang.String
def sayHello(){
'hello'
}
def message = sayHello()
println message // hello
You could note that while declaring the variable number
, I haven't specified a type. But during runtime, the variable number is of type java.lang.Integer
. Now if you assign a String object to number, it would start exhibiting the behaviours of String. Thus Groovy is strongly typed and dynamically typed. Similarly you could use def
keyword instead of a return type while declaring functions. You could also note that return
keyword is optional. The value of the last expression in the function will be returned to the caller.
In Groovy, you also get the ability to invoke methods dynamically in an elegant way.
String methodName = 'toUpperCase'
println 'groovy'."$methodName"() // GROOVY
def me = new Person(firstName: 'Naresha')
String property = 'firstName'
println me."$property" // NARESHA
The methodName can be assigned during runtime, which is the real usecase of this feature.
Null Safe Dereferencing
def me = new Person()
println me.firstName.toUpperCase()
As you expect, the above code would throw a NullPointerException
. A Java programmer would perform a not null check on firstName before trying to convert the firstName to uppercase. Groovy has a null safe dereference operator (?.). Let's see it in action.
def me = new Person()
println me.firstName?.toUpperCase() // null
me.firstName = 'Naresha'
println me.firstName?.toUpperCase() // NARESHA
Closures
In groovy, a closure is a block of code. One could assign a closure to a variable so that you could invoke them later. A closure can take arguments, return values and refer to variables in the lexical scope.
def add = { a, b ->
a + b
}
println add(10, 20) // 30
You could notice the syntactic sugar provided by Groovy to invoke a closure in the same way you would invoke a function. You could pass a closure as an argument to functions as illustrated in the following example.
def perform(a, b, operation){
operation(a,b)
}
println perform(10, 20, add) // 30
Native Support for List, Map and Range
Groovy provides elegant syntax for creating and working with List and Map objects.
def numbers = [1, 2, 3, 4]
println numbers.class //class java.util.ArrayList
// Add an element
numbers << 5
println numbers // [1, 2, 3, 4, 5]
//Get item at index 0
println numbers[0] // 1
Groovy uses the Java Collections library with some syntactic sugar.
def airports = [BLR: 'Bengaluru', DEL: 'New Delhi']
println airports.getClass().getName() // java.util.LinkedHashMap
//Add an entry
airports.BOM = 'Mumbai'
println airports // [BLR:Bengaluru, DEL:New Delhi, BOM:Mumbai]
//Get value for a key
println airports.BLR // Bengaluru
Note: airports.class
implies accessing the entry with key 'class`. Hence for maps use the long form.
The following example illustrates how you can create range objects.
def range = 1..4
println range // [1, 2, 3, 4]
println range.class // class groovy.lang.IntRange
def alphabets = 'a'..'z'
println alphabets
println 'x' in alphabets // true
println 'A' in alphabets // false
Internal Iterators
Groovy enriches Java Collections with some convenience methods, with internal iterators being the most common ones. The idiomatic approach in Groovy is to use the internal iterators, instead of for or while loops.
def numbers = [9, 7, 4 ,1]
numbers.each { number ->
println number
}
numbers.eachWithIndex { number, index ->
println "${index+1}) $number"
}
Note that each is a method, which accepts a closure as the argument. The equivalent code with named closure is as follows
def printNumber = { number ->
println number
}
numbers.each printNumber
Meta Programming
Metaprogramming is the ability to modify the behaviour of the program during runtime. Take the following code example.
def language = 'groovy'
println language.class // class java.lang.String
language.sayHello()
Since language is of type String, the method sayHello
is expected to be present in String class, for the code to work. Since String does not contain sayHello
method, the above code will throw a groovy.lang.MissingMethodException
. But in Groovy, you could add such methods as follows.
def language = 'groovy'
language.metaClass.sayHello = { ->
println "Hello from Groovy"
}
language.sayHello() // Hello from Groovy
'Java'.sayHello()
You could now invoke sayHello
on the variable language, but not on any other String objects. We can fix this as follows.
String.metaClass.sayHello = { ->
println "Hello from Groovy"
}
Now you could invoke sayHello
on any String object. If you want the message to customised based on the value you are invoking upon, you could achieve that as well.
String.metaClass.sayHello = { ->
println "Hello from $delegate"
}
A Real World Usecase
I was administering our team's wiki. The access was controlled by two configuration entries, one for read access and another for write access. I had to provide comma separated email ids against them. In our case, both entries were identical. The corporate email system was using the format of "Name@MyCompany.com", while the wiki expected it to be in "name@mycompany.com" format to work. I was getting individual ids through email or a list of them in an excel sheet, whenever new members joined the team.
To create the configuration entries, I had to convert the email ids to lowercase and separate them by comma.
I decided to maintain the active team members ids in a file input.txt in the first format (one id per line - handy when I copy from excel sheet). Here is a groovy script that would convert the contents of input.txt to comma separated email ids in the latter format.
import java.nio.file.Paths
def inputFile = Paths.get('./input.txt')
def output = inputFile.readLines().collect{
it.toLowerCase()
}.sort().join(', ')
Paths.get('./output.txt').text = output
println 'The result has been written to output.txt'
Whenever the team changed, all I had to do was update my input.txt, run the script and paste the contents of output.txt into read and write access configurations.
If you are using Groovy versions prior to 2.3, you have to use java.io.File
, instead of java.nio.file.Path
and Paths
Conclusion
Using Groovy can result in much smaller codebase at higher level of abstraction. Groovy has a number of features to empower developers with productivity benefits and is highly interoperable with Java.
[Originally published in December, 2014]