From Java to Groovy, part 2: Closures and Native Syntax for Lists
Learn about Groovy's native syntax support for lists, as well as discovering closures and their usefulness.
Join the DZone community and get the full member experience.
Join For FreeIn our previous installment, we've discovered how Java and Groovy's syntaxes are so close that you can even cut and paste valid Java code in your Groovy scripts. Along the way, we've also learned how to make your usual Java code much groovyier. Today, we'll continue this series, by learning about Groovy's native syntax support for lists, as well as discovering closures and their usefulness. To proceed, I'll steal the example class from Paul King's Groovy presentation, and we'll see how we can transform this Java class in Groovy.
The following Java class is also a valid Groovy class. Its purpose is to filter a list of names to remove the names which are longer than three characters. We'll create a list of names, we will call a utility method that is responsible of the filtering, and we'll print the result.
import java.util.*;
public class Erase {
public static void main(String[] args) {
List names = new ArrayList();
names.add("Ted");
names.add("Fred");
names.add("Jed");
names.add("Ned");
System.out.println(names);
Erase e = new Erase();
List shortNames = e.filterLongerThan(names, 3);
System.out.println (shortNames.size());
for (Iterator i = shortNames.iterator(); i.hasNext(); ) {
String s = (String) i.next();
System.out.println(s);
}
}
public List filterLongerThan (List strings, int length) {
List result = new ArrayList();
for (Iterator i = strings.iterator(); i.hasNext(); ) {
String s = (String) i.next();
if (s.length() < length+1) {
result.add(s);
}
}
return result;
}
}
We could certainly improve this Java example by using the Arrays#asList() method to save some lines. Anyway, we are going to follow the same path as in the previous article one more time to "groovyfy" this program. First of all, if you recall, we can get rid of semi-colons, but at the same time, to go a bit further, we are going to:
- use a nicer syntax for the for loop, more in line with the Java 5 for loop,
- we will also remove the imports, as Groovy imports java.util by default,
- the implicit default public modifier for methods and classes
- and we will use the println command
class Erase {
static void main(String[] args) {
List names = new ArrayList()
names.add("Ted")
names.add("Fred")
names.add("Jed")
names.add("Ned")
println names
Erase e = new Erase()
List shortNames = e.filterLongerThan(names, 3)
println shortNames.size()
for (String s in short_names) {
println s
}
}
List filterLongerThan (List strings, int length) {
List result = new ArrayList()
for (String s in strings) {
if (s.length() < length+1) {
result.add(s)
}
}
return result
}
}
Instead of using a class with a method main(), we're going to transform this code into a script, and we'll abandon the static typing information as well:
def names = new ArrayList()
names.add("Ted")
names.add("Fred")
names.add("Jed")
names.add("Ned")
println names
def shortNames = filterLongerThan(names, 3)
println shortNames.size()
for (s in shortNames) {
println s
}
def filterLongerThan (strings, length) {
def result = new ArrayList()
for (s in strings) {
if (s.length() < length+1) {
result.add(s)
}
}
return result
}
Instead of creating an instance of the Erase class, we just call the filterLongerThan() method. So far, these little transformations are not really new, since we had already followed these steps previously. What we are going to discover now is how to make the lists a little more friendly, thanks to Groovy's native syntax for lists. So how can we define a new list?
def names = []
And instead of adding an element one at a time to our list, we can fill it directly:
def names = ["Ted", "Fred", "Jed", "Ned"]
You can set and access elements with the subscript operator:
assert names[1] == "Fred"
names[1] = "Frederic"
Groovy also adds some useful methods on lists to simplify list activities such as enumerating the elements. Groovy does so by "decorating" the core JDK classes. Two handy methods added on lists are the each() method for iterating over all the elements, and the findAll() method to find all the elements matching some condition. At the same time, we will discover closures! Basically, without trying to provide a proper theoretical definition of closures, let's just think of closures as blocks of code, of statements, that can access all the variables or methods of the surrounding scope, and you can assign these blocks of code to variables, and pass them around elsewhere. Steven gives some more in-depth examples of closures in his post about higher-order functions. So now, I'm sure you want to see what a closure looks like?
def c = { println "hello" }
c()
See, that's simple, a closure is just a statement or a list of statements, delimited with curly braces. And you can assign it to a variable, and call this closure afterwards like a normal method call. A closure has a default implicit parameter called 'it'. But you can also provide a list of parameters, either typed or not typed. Also, like this is the case in methods, the last expression of a closure is the return value of the closure. But you can also use the return keywords, if you feel it is more readable.
def square = { it * it }
assert square(3) == 9
def isStringLongerThan = { String s, int i -> return s.size() > i }
assert isStringLongerThan("Guillaume", 4) == true
assert isStringLongerThan("Fred", 6) == false
// a more concise version could be:
// def isStringLongerThan = { s, i -> s.size() > i }
Now that we've discovered what closures look like and how you can assign them, and call them, we are going to see how we can pass a closure as a parameter to another method, as this is what we're going to do with the each() and findAll() methods that Groovy adds on collections.
def logCall(Closure c) {
println "Calling closure"
def start = System.currentTimeMillis()
println "Result: " + c()
def end = System.currentTimeMillis()
println "End of call (duration: ${end - start} ms)"
}
logCall({ return "Groovy is cool!" })
// but you can also remove the parentheses:
logCall { return "Groovy is cool!" }
We are now almost experts in closures, so let's put our knowledge into action by using the each() method on collections, to be able to call a closure on each element of the list. We are going to print all the names of the list:
shortNames.each({ String name -> println name })
// now without the parentheses
shortNames.each { String name -> println name }
// and with the implicit 'it' parameter
shortNames.each { println it }
After using each() to apply a closure on each element, we'll create a new list by filtering the original list of names according to a filter closure, thanks to the findAll() method. This method will find all the elements of a collection which match the criteria represented by the closure passed as parameter. This closure will be called on each element and the findAll() method will simply return a boolean value saying whether the current value matches or not. And in the end, a new list containing the elements that match will be returned. This closure will replace our filterLongerThan() method of our original Java class.
def shortNames = names.findAll { it.size() <= 3 }
Groovy provides several other methods of that kind, such as:
- find(): find the first element that matches
- every(): returns true if all the elements matches the criteria closure
- any(): returns true if at least one element matches
Now, it's time to finish our transformation of our initial Java class into a more concise Groovy class by applying what we have just learned about lists and closures:
def names = ["Ted", "Fred", "Jed", "Ned"]
println names
def shortNames = names.findAll { it.size() <= 3 }
println shortNames.size()
shortNames.each { println it }
For simple tasks like this list filtering, you can leverage Groovy's closures and native syntax for lists. At this point, you know how to write concise Groovy scripts by using its sensible defaults, its native syntax constructs, and by applying closures and collection facilities. In the next installements, we'll certainly stop the long process of transforming Java classes into Groovy scripts, but we'll learn about other Groovy syntax tricks for handling maps, ranges and regular expressions.
Opinions expressed by DZone contributors are their own.
Trending
-
Introduction To Git
-
Which Is Better for IoT: Azure RTOS or FreeRTOS?
-
What ChatGPT Needs Is Context
-
What Is JHipster?
Comments