Closure

Closure has usually been associated with functional languages. Groovy provides a very easy way of creating closure objects. A Groovy Closure is like a code block written in curly braces. Many people associate Closure to be an anonymous function in Java.

Closure in Groovy may accept arguments and returns a value. By default, the last statement in a Groovy Closure is the return statement. It means that if you are not explicitly returning any value from Closure, it will by default, returns the output of the last statement of Closure. Commonly, we define a Closure like this {argument list-> closure body}. Here, an argument list is a comma separated value that Closure accepts. Arguments are optional. If no argument is specified, then one implicit untyped argument named it will be available in the Closure body. The argument it will be null if no argument is supplied during Closure invocation.

In the following example, for the first call of Closure addTwo the variable it is assigned is 2, but in the second call, it is assigned null:

def addTwo = {it+2 }
addTwo(2)          // Output: 4
addTwo()          // NullPointerException

Alternatively, you can even declare a variable of type Closure. In Groovy, Closures are a subclass of the groovy.lang.Closure type:

groovy.lang.Closure closure1 = { println it }
closure1("This will be printed") // Output: This will be printed

To separate the Closure body from the argument list, we use the -> operator. The closure body consists of zero or more Groovy statements. Like methods, it can also reference and declare variables in its scope.

In the following code snippet, the addOne method was able to reference the constantValue variable in its scope, though it was defined outside of the Closure scope. Such variables are referred to as free variables. A variable which is defined within the curly braces of a Closure would be treated as a local variable:

int constantValue = 9
def addOne = { Integer a -> constantValue + a }

addOne(1)         // unnamed () invocation. Output: 10
addOne.call(1)    // call() invocation. Output: 10
addOne("One")     // MissingMethodException

In the preceding example, the argument of the Closure was of Integer type. With Closures, the statements within the curly braces are not executed until you explicitly invoke them, using either call() or by an unnamed () invocation syntax of Closure. In our example, the closure is declared in the second line, but it's not evaluated at that time. It will be executed if the call() method is explicitly made on the Closure. This is an important differentiator between Closures and code blocks. They may look the same, but they are not. Closures are only executed if the call() method is invoked on the Closure; not during its definition time. Remember, Closures are first class objects in Groovy, and can be referred using untyped variables or by using Closure variables. In both the cases, it is derived from groovy.lang.Closure. This class has overloaded call() methods with no or multiple arguments to invoke Closures.

When addOne Closure was called with an Integer as an argument, it executed successfully. However, for String type as an argument, it throws an exception. Also observe that the compiler didn't complain when we passed a String as an argument to the addOne Closure. This is because all arguments are checked at runtime; there is no static type checking done by the compiler.

The doCall() method on this Closure is generated dynamically, which accepts only Integer as an argument. So any invocation other than Integer type will throw an exception. The doCall() method is the implicit method, which cannot be overridden and cannot be redefined. This method is always invoked implicitly when we invoke call method or unnamed () syntax on a Closure.

We will conclude Closure by discussing the concept of delegate. This feature is widely used in Gradle. For example, when we define a repository Closure or dependency Closure in the build script, those Closures are executed in the RepositoryHandler or DependencyHandler classes. These classes are passed to the closures as delegates. You can refer to the Gradle API for more details. Let us not complicate things here. We will try to understand the concept with simple examples.

Consider the following example, where we are trying to print a myValue variable, which is undefined in the class. Obviously, this call will throw an exception as this variable is not defined in the scope:

class PrintValue{
  def printClosure = {
    println myValue
  }
}
def pcl = new PrintValue().printClosure
pcl()   //Output: MissingPropertyException: No such property

There could be a situation where we want to execute this closure against another class. This class can be passed to the closure as a delegate:

class PrintHandler{
  def myValue = "I'm Defined Here"
}

def pcl = new PrintValue().printClosure
pcl.delegate = new PrintHandler()
pcl()

OUTPUT: I'm Defined Here

In this example, the PrintHandler class has defined the myValue variable. We have delegated and executed the closure against the PrintHandler class.

So far, it is working as expected. Now, what if myValue is redefined in the PrintValue class:

class PrintValue{
  def myValue = "I'm owner"
  def printClosure = {
    println myValue
  }
}

In this scenario, on executing the Closure, we will find the output as I'm owner. This is because, when closure was trying to resolve the myValue variable, it found the variable defined within the scope of the owner (the PrintValue class, where the Closure is defined), so it didn't delegate the call to the PrintHandler class. Formally, this is known as OWNER_FIRST strategy, which is the default strategy. The strategy resolves this way—the closure will be checked first, followed by the closure's scope, then the owner of the closure, and, finally, the delegate. Groovy is so flexible that it provides us with the capability to change the strategy. For example, to delegate the call to the PrintHandler class, we should specify the strategy as DELEGATE_FIRST:

def pcl = new PrintValue().printClosure
pcl.resolveStrategy = Closure.DELEGATE_FIRST
pcl.delegate = new PrintHandler()
pcl()

With the DELEGATE_FIRST strategy, the closure will try to resolve the property or methods to the delegate first and then the owner. The other important strategies are:

  • OWNER_ONLY: It attempts to resolve the property or methods within the owner only and doesn't delegate.
  • DELEGATE_ONLY: Closure will resolve the property references or methods to the delegate. It completely ignores the owner.
  • TO_SELF: It will resolve the property references or methods to itself and go through the usual MetaClass look-up process.

This was indeed a very short description. I suggest you to refer to the Groovy documentation for more details at: http://docs.groovy-lang.org/latest/html/api/groovy/lang/Closure.html.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.21.106.7