1) Introduction to Groovy Closure
In this article, let us look into one of the important features supported in Groovy, the Closures. Groovy is an object-oriented scripting language in which the syntax resembles Java. Not all languages support the concept of Closures directly, although they may provide indirect support, that too with so many limitations and restrictions. This article will make you familiar with the concepts of Groovy Closures and will provide considerable amount of code snippets to make the understanding clearer. This article assumes that the reader is having sufficient knowledge in Groovy Programming and first-time readers may look into the Introductory Article on Groovy before proceeding with this article.
also read:
2) Closures
To say in simple words, a Closure definition is a block of code that can be re-used any number of times. The definition may look more or less like the definition of a function or a method. A Closure resembles a function or a method in many aspects, however there are subtle differences between them. For example, a Closure may take any number of arguments like a function. A Closure has a return value like a method. A closure can be re-used and referenced any number of times and anywhere similar to a method.
It is clear that a Closure is very similar to a function in many aspects, but the difference is that a Closure can be passed as an argument to a function. A programming language like Java supports similar kind of Closures through anonymous Inner classes. For example, consider the following piece of code,
Button button = new Button(); button.addActionListener(new ActionListener(){ public void actionPerformed(ActionEvent actionEvent) { // Do something here. } });
In the above code, the method Button.addActionListener()
will take an argument of type ActionListener
. We have defined something which is of type ActionListener
and has included a method actionPerformed()
within it. This type cannot be used elsewhere in the code since we don’t have any reference to it. One possible way to re-use the reference is to have something like the following,
ActionListener actionListener = new ActionListener(){ public void actionPerformed(ActionEvent actionEvent) { // Do something here. } }; Button button = new Button(); button.addActionListener(actionListener);
To understand the need to have Closures, let us analyze the situation in this way. You have some simple piece of logic (such as printing the state of an object) and you want this logic to be re-used by some common group of objects. The more traditional way to arrive at the solution for this problem is to define a method with suitable argument list and then to call this method. However, in an object-oriented programming language like Java, methods doesn’t exist on their own. They have to be embedded within a class. It doesn’t look nice to create a new type (in the form of a class) and to embed a simple behavior in the form of a method. This is where Closures come in. They are simply a named type for some set of statements containing some useful logic. The named type can then be re-used any number of times.
The same problem exists with callbacks. Classes with the design of call-backs in mind, usually have a well-defined interface along with methods. And the client application (or the caller) has to define an implementation class conforming to the interface which often results in defining new types. This will result in the many smaller classes in the Application. One form of solution to these kind of problems is manifested in the form of Closures.
3) Groovy Closures
3.1) Simple Closures
Let us look into the syntax for defining and making use of Closures. The syntax of a closure definition looks like this,
ClosureName = { Parameter List -> Set of statements. }
The syntax is simple (although it may not look like that at the very first glance). Let us get into the example. If we want to define a closure called 'helloClosure'
, then we can define something like this,
helloClosure = { println "Hello"; }
Note that in the above definition, the closure name is 'helloClosure'
and since we don’t want this closure to accept any parameters, we have left the list of parameters that should follow after the brace '{'
as blank. Next follows the block section which may contain any number of statements.
Let us look into the following sample,
SimpleClosure.java
public class SimpleClosure { def testClosure = { println "Test Closure"; } public static void main(String[] args) { ClosureTest object = new ClosureTest(); object.testClosure(); } }
We have defined a closure called 'testClosure'
with the 'def'
(meaning for definition) keyword. This closure doesn’t accept any arguments. Simply defining the closure doesn’t provide any purpose unless someone calls that explicitly. In the main method, we have created an object for the class 'SimpleClosure'
and have invoked the closure 'testClosure'
in function-like syntax.
3.2) Closures with Arguments
In this section, let us see how to define Closures that takes arguments. As we have already seen, Closures, like functions may take any number of arguments. For example, consider the following code snippet,
ClosureWithArguments.java
public class ClosureWithArguments { def addClosure = { int one, int two -> println "Number one is " + one; println "Number two is " + two; return one + two; } public static void main(String[] args) { ClosureWithArguments object = new ClosureWithArguments(); def result = object.addClosure(10, 20); println "Result from closure is " + result; result = object.addClosure(130, 230); println "Result from closure is " + result; } }
In the above code, we have defined a closure called 'addClosure'
. Note the parameter list (int one, int two) that is following after the brace '{'
. Following that is the symbol ‘->’ which is used as a separator between the parameter list and the set of statements to follow. The code prints the arguments passed to this function before returning the result.
The caller (main method) calls this Closure using the same function-like syntax passing two arguments.
3.3) Different ways of calling a Closure
We have seen how to invoke or call a Closure in the above sections. For example, if the Closure Name is 'myClosure'
, then this closure can be invoked through the expressioin 'myClosure()'
.
CallingClosures.java
public class CallingClosures { def testClosure = { println "Test Closure"; } public static void main(String[] args) { CallingClosures object = new CallingClosures(); object.testClosure(); object.testClosure.call(); object.testClosure.doCall(); } }
However, there are other alternative ways of calling a Closure as mentioned above. Every closure has default methods (implicit methods, so no need to declare them) namely call()
and doCall()
so we can also call the closures in this manner.
3.4) The Closure class
It should be noted that all Closure definitions will be resolved to some Java class at run-time. So all Closures are just like any other objects. And it is not surprising that the class name of such closure objects seems to be 'Closure'
. It means that the following code will work fine.
ClosureClassTest.java
public class ClosureClassTest { def testClosure = { println "Test Closure"; } public static void main(String[] args) { ClosureClassTest object = new ClosureClassTest (); Closure closureObjectForTest = object.testClosure; closureObjectForTest(); closureObjectForTest.call(); closureObjectForTest.doCall(); } }
The above code tries to assign the closure 'testClosure'
to the object 'closureObjectForTest'
which is of type 'Closure'
. This is perfectly legal, since all Closure objects are of type 'Closure'
.
3.5) Closure Currying
At times, we may need to create multiple number of Closure objects whose argument lists to some extent will be similar. For example, consider the following Closure 'getNoOfRuns'
which takes two arguments, one is the name of the country and the other one being the name of the player. And we wish to make Closure calls four times with the following values,
- Country-> India, PlayerName-> Sachin
- Country-> India, PlayerName-> Ganguly
- Country-> Pakistan, PlayerName-> Afridi
- Country-> Pakistan, PlayerName-> Salman
Examine the first two set of invocations. Both are taking the value 'India'
as the first argument. And in the 3rd and the 4th invocation, the argument value 'Pakistan'
is common. Now, analyze the following code where we will see a better way of making invocations to the Closure.
ClosureCurrying.java
class ClosureCurrying { def getNoOfRuns = { String country, String playerName -> if (country.equals("India")) { if (playerName.equals("Sachin")) { return 15806; } else if (playerName.equals("Ganguly")) { return 11319; } } else if (country.equals("Pakistan")) { if (playerName.equals("Afridi")) { return 5226; } else if (playerName.equals("Salman")) { return 1159; } } return -1; } public static void main(String[] args) { ClosureCurrying object = new ClosureCurrying(); def indianTeam = object.getNoOfRuns.curry("India"); def result = indianTeam("Sachin"); println "No. of runs scored by Sachin " + result; result = indianTeam("Ganguly"); println "No. of runs scored by Ganguly " + result; def pakistanTeam = object.getNoOfRuns.curry("Pakistan"); result = pakistanTeam("Afridi"); println "No. of runs scored by Afridi " + result; result = pakistanTeam("Salman"); println "No. of runs scored by Salman " + result; } }
We have referenced the Closure 'getNoOfRuns'
and has called the method 'curry()'
on that with the value 'India'
as its argument. Let us imagine that the method 'curry()'
will create a new object with the countryName being set to 'India'
. Now if want to get the number of runs for the player 'Sachin'
, then simply invoke the Curried Closure (the Closure that was obtained after calling the 'curry()'
method) with the value 'Sachin'
. The same applies for 'Ganguly'
and for the rest of the players.
3.6) Closures as Arguments to another Functions
Till now, we have seen how to define and call Closures. In this section, we will see how to pass a Closure as an argument to some other function. Consider the following code snippet,
PassingClosuresTest.java
class PassingClosuresTest { def testClosure = { println "Test Closure"; } void acceptClosure(Closure closure) { closure.call(); } public static void main(String[] args) { PassingClosuresTest object = new PassingClosuresTest(); Closure closureObject = object.testClosure; object.acceptClosure(closureObject); } }
In the above sample, we have defined a Closure called 'testClosure'
and have defined another method which is taking an argument of type 'Closure'
. In the main method, we have assigned the 'testClosure'
to the object 'closureObject'
which is of type 'Closure'
and the same is passed to the method 'acceptClosure()'
method.
3.7) Making use of Closures
In this section, let us make use of Closures being passed as arguments. For example, consider the following Class which wraps the string array objects. The decorate()
method accepts an argument of type 'Closure'
. The logic within the method iterates over the strings and the Closure being passed as argument to this method is invoked by passing the iterated resultant string object itself as argument.
DecoratedStringArrayClosureTest.java
class DecoratedStringArrayClosureTest { def strs = null; public DecoratedStringArrayClosureTest(def strs) { this.strs = strs; } public void decorate(Closure closure) { for (String temp in strs) { closure.call(temp); } } def DecoratedClosure = { String element -> println "##" + element + "##"; } public static void main(String[] args) { def numbers = ["one", "two", "three"]; DecoratedStringArrayClosureTest object = new DecoratedStringArrayClosureTest(numbers); Closure decoratedClosureObject = object.DecoratedClosure; object.decorate(decoratedClosureObject); } }
The Decorated Closure decorates the string passed to it by surrounding the string with ‘#’ symbols.
4) Conclusion
In this article, we understand the need for Closures and saw how Closures stand as a better replacement for defining a new type even for simpler cases. We also dealt in detail about the various usage cases of Closures in Groovy Scripting Language along with plenty of code samples.
The following are some of the popular articles published in Javabeat.Please read the articles and post your feedback about the quality of the content.
If you are interested in receiving the future java articles from us, please subscribe here.