Groovy Tutorial for Java Developers – Part 3: Collections

This tutorial aims for developers that are already familiar with Java. It focuses on Groovy specific features and does not explain basic programming knowledge.

This part of the Groovy tutorial series focus on the usage of lists and maps in Groovy. It will require an understanding of Groovy closures. If you are not familiar with closures in Groovy, you can head to the second part of this tutorial to learn about them.

Lists

Groovy offers a shortcut method to create a new list:

def list = [1,1,2,3,5]

This will create an ArrayList with the given numbers in it. To access a list element you can use the overloaded array index accessor:

println list[0] // will print 1
println list[-1] // negative indexes for access from the end of the list, will print out 5

Finding Elements

To check whether a specific element is contained inside a list you can use the in operator:

if (4 in list) { ... }

Transformation and Filtering

Due to the existance of closures (as explained in the previous tutorial part) there are easy ways to iterate, filter and transform lists. To iterate over a list you can use the each method on a list:

list.each {
  println it
}

As you see we made our code more groovyish by omitting the method call parantheses and just pass the closure. The passed closure will be executed once for each element in the list and the list element will be passed as a parameter (here implicitly it) to the closure.

Note that all of these methods work on any Java Collection. No matter if you created it the short way as shown above or if comes from a Java part of your program and has been created the “traditional” way.

To filter a list you can use the method findAll:

def even = list.findAll { it % 2 == 0 }

The findAll method creates a new list, by calling the passed closure for every element in the list and adding it to the new list if the closure returns a value that evaluates to true (see first tutorial part for Groovy truthy).

If you are just interested in the first element of the list that matches a given criteria, you can use the find method instead. It also takes a closure and will return the first element for which the closure evaluates to true.

If you need to transform each element of a list you can use the collect method:

def squaredList = list.collect { it * it }

This will create a new list by transforming each element in list by the given closure.

If the only transformation you want to do in the collect closure is calling a method on each element, like in the following:

def upper = ["Hello", "World"].collect { it.toUpperCase() }

For these cases Groovy has again a syntactic sugar operator called the spread-dot operator. You can express the above with the operator as follow:

def upper = ["Hello", "World"]*.toUpperCase()

Beside the mentioned methods the Collection (and Iterable) interfaces provide some more methods, like any or every to check whether any element or every element inside the list fullfills a specific condition (expressed by a closure). To see all further methods you can check the Collection documentation in the Groovy docs.

Maps

Similiar to lists Groovy also has some more convenient method to instantiate maps.

def key = 'Key3'
def aMap = [
  'Key1': 'Value 1', // Put key1 -> Value 1 to the map
  Key2: 'Value 2', // You can also skip the quotes, the key will automatically be a String
  (key): 'Another value' // If you want the key to be the value of a variable, you need to put it in parantheses
]

After this initialization you will get a new LinkedHashMap with the entries: Key1 -> Value1, Key2 -> Value 2, Key3 -> Another Value. If you want to create an empty map with this syntax you can write: def map = [:]

To access entries in the map you can again use square brackets:

println aMap['Key1'] // Access the key Key1
println aMap[key] // Access the entry with the value of key variable

Beside using brackets you can also use dot notation to access entries:

println aMap.Key1 // Access Key1 entry

If the key has characters in it, that can’t be parsed as an identifier (e.g. a space) you can still use the dot syntax with quotes around it: aMap.'Key With Spaces'. Since in my opinion this looks kind of strange I don’t give it its own box. You can even use interpolated Strings with this syntax, like: aMap."$key", which is the same as aMap[key].

Transforming and Filtering

For filtering and transforming tasks you have the same methods than for lists with a slightly different syntax:

map.each {
  println it.key
  println it.value
}

Instead of the value you will get the entry of the map on which you can access the key and value as shown above. The each method can also take a closure with two parameters in which case it will put key and value into these parameters. So you could express the above also as:

map.each { key, value ->
  println key
  println value
}

The same rule applies for the find, findAll, any and every. You can either accept one argument in the closure in which case you will get the entry or you can directly accept the key and value separated. Any and every return again a boolean value, findAll will create a new map ony containing the entries, for which the closure evaluated to true, and the find method will return the first entry (and not just the value) for which the closure evaluated to true.

The only method that behaves quite different is the collect method. The collect method will (also on maps) create a list as an output. It will iterate over all entries in the map and pass them (as a single or as two parameters) to the closure. The objects you return from the closure will be added all to a list (and not a map). That way the following does not work as someone might expect:

map.collect { return it }

This will result in you having an ArrayList of Map entries, which most likely is not what you want. You can use the collect method if you somehow want to collect a map to a list of values. If you want to transform a map into another map you have to use the collectEntries method:

map.collectEntries {
  return it
}

You can return a map entry from the closure, which will be put to the new map (so the example above will just create the very same map again). If you want to create new entries you can return a map (most often with just one entry) from the closure:

map.collectEntries { key, value ->
  return [(key): value]
}

This snippet does the same as the one above. It will just add every entry again to the new map. You can even return a map with more then one entry. In this case all entries in the map will be put to the new map:

map.collectEntries { key, value ->
  def newKey = key + "^2"
  return [(key): value, (newKey): value*value]
}

This snippet will add each entry and a new one with the original key suffixed by ^2 and the value squared to a new map.

What’s next?

This was the last part of this short tutorial series on Groovy for Java developers. Groovy offers a lot more complex mechanisms than explained here. Especially intersting might be techniques like operator overloading and meta classes (which allow to add or overwrite methods on any existing class). If you want to learn more you can head to the official Groovy documentation. I hope you enjoyed the tutorial and if you wish for more parts on some more specific topics, feel free to leave a comment below.