Groovy example for Java developers

Posted by Jacob Ibáñez Sánchez on February 25, 2018

The intent of this article is to show Java developers how powerful, concise and declarative Groovy is with a simple example. I hope this will whet your appetite for more Groovy fun! If that’s the case, my recommendation is to take a look at the official documentation.

The problem

I want the content of a simple csv file to be transformed to a list of maps. Each item of the list is a row of the csv transformed to a map, ignoring the first one (which contains the headers). The keys of the map are the headers of the csv and the values, the row itself. All of them, obviously, split by a given separator.

This comes handy for many reasons. One of them is that this format is the same used by Groovy for returning database resultsets. As you’ll see right below, accessing data from lists and maps is very easy and intuitive with Groovy.

A Groovy solution

And here, a possible groovy solution! Please try to read the code and understand it by yourself before reading the comments below. You’ll find it surprisingly…​ groovy!

Whoa! That was a lot of magic! First of all, the approach I’ve chose to complete the task is to add a method to the java.io.File class that returns the desired result. The reason why I’ve chose such an unusual approach is simple: I can do it! Well, it’s also the most obvious one, why do I have to implement such a method anywhere else? The java.io.File class is the perfect place to be. In Groovy, you can achieve that in multiple ways.

A taste of Groovy

But first, let’s take a closer look to this code:

1
2
3
4
    File thisFile = delegate as File    (1)
    def allFileLines = thisFile.readLines()    (2)

    isCsv thisFile    (3)
  1. So many things in so little lines of code! Firstly, you can see two ways of declaring variables in Groovy; with explicit typing and without it. The first way is equals to the java way. You declare the type of the variable, its name and its value. In this case, the value is the delegate of the closure (you can see that the delegate of a closure is like the self object). You can see as well the type coertion here with the as keyword. It’s basically like java coertion but with subtles differences out of the scope of this post.

  2. The second way is with the def keyword. You don’t declare the type of the variable and the compiler can guess it for you. Does it means we can reuse the variable with several types? No! Groovy is a strong typed language, but it does the type checking at run time instead of at compile time. Well, in fact you can change that behaviour, but that’s a different kettle of fish! This variable is storing all the lines of the file by calling the File.readLines() method (which I think is amazing). You’ll see that Groovy has enhanced the Java platform with a bunch of methods everywhere. You can check them here.

  3. Last but not least, we have a call to a method (a Closure, actually) declared in the first three lines of the script. Can you see that the method call is practically like written English? With good naming of methods and variables and the Groovy optional parentheses when calling to methods, this is easily achieved. The isCsv Closure simply asserts that the file name’s ends with the csv pattern.

The Glory of Metaprogramming

Now let’s see how we can add a method to an existing class without touching that class!

Listing 1 - Adding the method to the File class
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//add a new method to the File class, dynamically, accessing its metaclass
File.metaClass.toResultSet = { separator = ',' ->   (1)

    File thisFile = delegate as File
    List allFileLines = thisFile.readLines()

    isCsv thisFile

    allFileLines.with {   (2)
        def header = head().split(separator)    (3)
        def rows = tail().collect { it.split(separator) }    (4)

        rows.collect { row -> [header, row].transpose().collectEntries() }    (5)
    }
}
  1. Here we are accessing the java.io.File metaclass and adding the method directly, called toResultSet. The method implementation is declared as a Closure, which is an object that contains behaviour in favour of state. Being an object, a Closure is a first class citizen and can be passed around. You can think of it as a Java 8 lambda, but a Closure is by far better!

  2. You’ve seen before that the File.readLines() method reads all the lines of a file. Groovy manages the i/o boilerplate for you. The with method here (available for all Groovy objects) receives a Closure as an argument with a special scope. By default, you have direct access to the caller (the list of strings in the current example). It makes successive calls to an object cleaner.

  3. As said before, since you have direct access to the list of lines of the file, you can access directly to the head method, which returns the first line of a collection. Note that we are storing this first line in a variable called header, with the def keyword. Remember that in Groovy you don’t have to type the type (pun intended), the compiler will guess it for you!

  4. Again, we are directly accessing to the tail method, which returns all the elements of a collection, except the first one. We are also collecting a new list, applying a Closure that split each line with a given separator. The it variable is the content of each element of the collection. Every Closure has this implicit variable, which you can rename as you wish (and please, do it for the sake of clarity!).

  5. A lot of things are happening here! First of all, you’re returning something without the return keyword. It’s completely optional. In addition, inside of a Closure, the result of evaluating the last line is always returned. But, what are we returning? Well, we are returning a list of maps, which was our first goal! In one line you’re collecting all the lines of a csv, except the header, split by a separator, transposing its header to each element, and then collecting the result to a map. Amazing. It might look confusing at first, and you can achieve the same result in other ways, of course! But the power of Groovy lets you do such a transformation in only one line of code, and it’s absolutely readable.

Now we have our brand new toResultSet method added to the java.io.File class, let’s use it!

Listing 2 - Using our brand new method
1
2
3
4
5
6
7
8
9
10
11
12
13
14
//create a fake csv file for testing the code
def resultSet = new File('fake.csv').with {
    newWriter().withWriter { writer ->
        writer << [    (1)
                ['Header1;Header2;Header3;Header4'],
                ['CellA1;CellB1;CellC1;CellD1'],
                ['CellA2;CellB2;CellC2;CellD2'],
                ['CellA3;CellB3;CellC3;CellD3']
        ].flatten().join('\n')
    }
    deleteOnExit()
    //call the brand new method we've just created!
    return toResultSet(';')    (2)
}
  1. We create a fake csv file for that purpose. Again, using the with method, we are working with a new instance of a File, but never assigning the instance to a variable. We call the newWriter method, that returns a writer on the file, and then using the withWriter on said writer. All these methods are dealing themselves with the opening and closing of streams, etc. Also, you can see an example of the renaming of the default it variable. We are renaming it to writer. You can see the Groovy notation for creating lists as well. A simple pair of square brackets are enough for declaring a list. But, what kind of list? By default, Groovy creates an ArrayList.

  2. Now we are explicitely returning a value from a Closure, and the value we are returning is the result of our new method! Note we are passing a parameter to the method, which is the separator. If you check back the declaration of the method, you’ll see this code: separator = ',' →. That’s, let’s say it this way, the method signature. But not only that, we’re declaring a default value for the separator argument! Fantastic!

Testing our work

Let’s check that our method is working as expected!

Listing 3 - Testing our code
1
2
3
4
5
6
7
8
9
assert resultSet[0] == [    (1)
        Header1: 'CellA1',
        Header2: 'CellB1',
        Header3: 'CellC1',
        Header4: 'CellD1'
]
assert resultSet[0].Header2 == 'CellB1'    (2)
assert resultSet*.Header2 == ['CellB1', 'CellB2', 'CellB3']    (3)
assert resultSet.Header1[2] == 'CellA3'    (4)
  1. If we take the first line of our resultset (hey! that’s an array notation for accessing a list position? yup! it certainly is!), we obtain a map with the first line of the csv file. Note the map creation notation; as with lists, it’s far more simpler than Java. Square brackets surrounding key : value pairs makes the job!

  2. Dot notation to the rescue here! If the first line is a map, the map.key notation gives you access to the value associated to that key.

  3. What’s that asterisk here? It’s optional, but it denotes that we are accessing at all elements of the list at the same time. And since we are calling at the Header2 key, we are retrieving all elements of the second column of the csv.

  4. Here, like in the previous example, we are accessing to all elements of the third column, without the asterisk (it was optional, do you remember?), and then to the third element of the third column, again with the square brackets notation.

Conclusion

Eazy peazy lemon squeezy! Just think about the lines of code needed to achieve the same result in java. With lambdas they’re obviously decreased, but Groovy is still cleaner and more concise. I’ve skipped a lot of things and you’ve only seen the gist of Groovy, but you can easily intuit its real power.

One thing you have probably noticed is that there are no imports in the above code snippet. You can paste the code in a file and run it with the groovy CLI and it will run without complains. Java automatically imports the contents in the java.lang package, and Groovy also import other packages, like the java.io one.

Please, feel free to leave any comments and suggestions! Hope you liked it!


Comments