🔥 🔥Practical Open Source is coming 🔥🔥 Propose an article about doing business with Open Source!

Boost your Apache Groovy skills with closures

As a developer working with Apache Groovy, understanding closures is crucial. Closures offer a concise and powerful way to write clean, functional code. They can encapsulate logic and data within a block, allowing you to pass functionality as arguments to methods or create custom data structures on the fly. This flexibility simplifies complex operations and promotes code readability, making you a more efficient Groovy developer. (If you haven’t installed Groovy yet, please read the intro to this series.)

Let’s take a look at what might be Groovy’s most important introduction in its mission to streamline things for Java programmers — the Closure.

Don’t believe me? Here’s what Venkat Subramaniam said about it in his 2008 book “Programming Groovy – Dynamic Productivity for the Java Developer,” (since updated in 2013 here):

Closures are one of the Groovy features you’ll use the most… one of the biggest contributions of the GDK is extending the JDK with methods that take closures.

Of course, Groovy didn’t invent the closure concept; according to the Wikipedia article on closures,

The concept of closures was developed in the 1960s for the mechanical evaluation of expressions in the λ-calculus and was first fully implemented in 1970 as a language feature in the PAL programming language to support lexically scoped first-class functions.

It’s that “first class functions” part that’s especially applicable to the functionality Groovy brings to the table. In this article, I’m going to look at the kinds of things you can do with those extended JDK “methods that take closures”. In the next article, you’ll learn more about writing some of those methods yourself.

One of the really useful extensions in GDK is the addition of various methods that take closures to the Collectio``n, List, Map and other related interfaces. One of the first such methods that new Groovy programmers run into is each. Here’s a simple example:

1  def l = [1,2,3,4,5] 
2  l.each({ int i ->
3    println "i = $i"
4  })

Let’s review this carefully:

Line one defines an ArrayList instance, l, with five elements whose values are all integers.

Line two calls the each() method on l. Each call iterates through the elements of l and calls the Closure instance passed as an argument, passing the current element of l as an argument to it. The Closure instance is the { … } construct, starting on line two and ending on line four. Groovy treats what looks like sort of like a C “block” — statements between { and } – as a call to new Closure(). This is some of that nice syntactic support for often-occurring structural patterns. Notice the int i -> is how one goes about declaring a parameter to the Closure instance.

Line three is the actual executable body of the Closure instance. In this case, just printing out the parameter i.

Line four ends the closure and closes the each() call.

When you run this, you see:

$ groovy Groovy14a.groovy
i = 1
i = 2
i = 3
i = 4
i = 5

Readers familiar with lambdas in Java should recognize this structure. But it’s worth mentioning that Groovy closures predate Java lambdas.

This is a good point to remind the reader that parentheses around arguments to a method call are unnecessary. Nor is it necessary to define the type of the closure’s parameter(s), since Groovy is a dynamic language and figures that stuff out at run time.

Groovy closures offer some additional benefits, too. One in particular is that a Groovy closure can access anything in the scope outside the closure body. But Java lambdas can only access things that are “effectively final.” Here’s an example of using this to sum a list of numbers:

1  def l = [1,2,3,4,5]
2  int lsum = 0
3  l.each { i ->
4    lsum += i
5  }
6  println "lsum = $lsum"

Which, when run, looks like:

$ groovy Groovy14b.groovy
lsum = 15

Of course, this approach is generally frowned upon because it requires the mutability of the variable lsum. This is seen by some as causing maintainability issues. This happens when mutable variables are used unnecessarily. It could end up being tampered with in a later maintenance cycle.

To avoid needing a mutable lsum, you can use the inject() method instead of each():

1  def l = [1,2,3,4,5]
2  final int lsum = l.inject(0) { ps, i ->
3    ps + i
4  }       
5  println "lsum = $lsum"

For those following in the Groovy GDK documentation and wondering why they cannot find inject() in the list of List methods, inject() is defined in the Iterator interface.

Let’s dig into that line 2 a bit:

  • You declare lsum to be final, meaning its value cannot be changed once assigned.
  • The inject() method takes two arguments: a starting value, here 0, and an instance of a Closure, and iterates over the elements of l.
  • The Closure instance provided to inject() takes two parameters — the first parameter is, ps, meaning “partial sum”, being the result so far. The second parameter, i, is the value of the current element being examined. It returns the new result after dealing with the current element, here summing the two parameters.

When you run this script, you see:

$ groovy Groovy14c.groovy
lsum = 15

For those familiar with the map — reduce paradigm, Groovy’s inject() implements the reduce operation. This brings you to the map operation, called collect() in Groovy. You can use collect() to map the elements of the list l to their squares:

1  def l = [1,2,3,4,5]
2  def lsq = l.collect { i ->
3    i * i
4  } 
5  println "lsq = $lsq"

Here you can see that collect``() takes one argument, namely the list element being examined, and returns its transformed, or mapped, value. Run this:

$ groovy Groovy14d.groovy
lsq = [1, 4, 9, 16, 25]

Another extra cool method that provides a filter ability is the findAll() method. Use findAll() to filter the odd numbers out of the list:

1  def l = [1,2,3,4,5]
2  def lo = l.findAll { i ->
3    i % 2 != 0
4  } 
5  println "lo = $lo"

Here you can see that findAll() takes one argument, namely the list element being examined. It returns a list whose elements satisfy the test expression provided in the closure body. Run this:

$ groovy Groovy14e.groovy
lo = [1, 3, 5]

The method findAll() finds all the elements satisfying the test condition in the closure supplied. You might not be surprised to learn there is also a find() method that finds the first element and then stops.

I’m not going to go through all the fine List methods that take closures as arguments, but here are two I find useful:

  • split() looks similar to findAll() but returns a list containing two sublists. The first sublist contains the elements that yielded a true value in the test expression. The second sublist are test expressions that yielded a false value.
  • takeWhile() also looks similar to findAll() but returns a list containing all the elements that yielded a true value in the test expression until the first false value was found.

Moving to the Iterator interface, one of the most useful closure-taking-methods of all is collectEntries(). This can be used to build a Map from a list of two-element sublists in combination with the transpose() method of the List interface, as follows:

1  def keys = [1,2,3,4,5]
2  def values = ["i","ii","iii","iv","v"]
3  def map = [keys,values].transpose().collectEntries { k, v ->
4    [(k): v]
5  }
6  println "map = $map"

Lines one to two define two lists, the first list contains the intended keys of the map. The second list applies the values to be assigned to those keys.

Line three creates a list of the two sublists of keys and values and calls transpose() on them to pair up the key-value pairs. This creates a list of five two-element sublists [1,”i”], [2,”ii”’] and so on. It also calls collectEntries(), passing it a closure that converts each two-element sublist into a MapEntry instance.

Line four returns a Map instance containing one MapEntry. Recall that in Groovy declaring a Map instance using the syntax [a: b] treats a as a String. But [(a): b] causes a to be evaluated.

When you run this, you see:

$ groovy Groovy14f.groovy
map = [1:i, 2:ii, 3:iii, 4:iv, 5:v]

Of course, the Map interface also defines a bunch of interesting methods that take Closures as parameters:

  • each() iterates over each MapEntry in a Map, calling a closure and passing a MapEntry to a single-parameter closure or key and value to a two-parameter closure.
  • inject() similarly applies the reduce method to a map.
  • findAll() iterates over and filters a Map.
  • findResults() similarly iterates over a Map, filtering out null results.
  • groupBy() iterates over a Map, combining groups of MapEntries into sub-maps based on calculated key values. For example, a map with integer keys could be split into two sub-maps, one with the key “even” and all the MapEntry instances with even-numbered keys. The rest goes into another sub-map with the key “odd”.

This is a good point to suggest a diversion to the reference descriptions for Collection, List, Map and Interface, conveniently found together in the Groovy GDK documentation. You should also read the Groovy documentation on Closure.

Conclusion

Groovy closures predate Java lambdas and offer a few interesting advantages. They aren’t restricted to referring to effectively final variables in the containing scope. They are easier to learn (from my perspective at least) since they don’t depend on a new and complicated set of classes involving streams, functions and the like. Instead their utility is attached to well-known JDK classes including List and  Map.

And, as usual, Groovy provides some powerful syntactic shorthand to make closure-based code clean and simple to read, facilitating maintenance down the road. Stay tuned for more!

Disclaimer: All published articles represent the views of the authors, they don’t represent the official positions of the Open Source Initiative, even if the authors are OSI staff members or board directors.

2 responses to “Boost your Apache Groovy skills with closures”

  1. […] explored their power, now get ready for a deep dive into using closures with Groovy’s rich ecosystem of libraries. […]

  2. […] that you’ve mastered Groovy Closures, let’s see how they empower you to sort data more concisely and flexibly compared to […]

Author

Support us

OpenSource.net is supported by the Open Source Initiative, the non-profit organization that defines Open Source.

Trending