-
Notifications
You must be signed in to change notification settings - Fork 0
Closures
- Summary
- Comparison to Lambdas
- Syntax
- Invocation
- Parameters
- Closures as Parameters
- Delegation
- Functional Programming
- References
- Notes
Groovy closures are true closures, with access to the variables in the context in which they are defined. They are:
-
Contained in braces
-
They are functions.
-
But they are also instances of the Closure class.
-
Used in callbacks
-
The default parameter (if you do not provide parameters) is
it.[1,2,3].each { println it }
Syntactically, closures look a lot like lambdas, and they can be passed as parameters to another function. In other words, they are first-class objects. However, closures are instances of the Closure class, making them very different from Java 8 lambdas.
In addition, delegation is a key concept in Groovy closures which has no equivalent in lambdas. The ability to change the delegate or change the delegation strategy of closures make it possible to design beautiful domain specific languages (DSLs) in Groovy.
{ <type> param1, <type> param2 ... ->
//Body
}- Again, parameters are optional.
- If no parameters are listed, the default parameter is
it. - Parameters, if present, are listed (comma-separated) immediately after the first brace, on the first line of the closure, and followed by a arrow.
- The code body begins on the next line.
- The output of the last line is the return value, with or without the
returnstatement.
Closures can be invoked like any other function:
closureName(<params...>)Alternatively, closures can be invoked with the call method of the Closure class:
def c = { 123 } // returns 123
assert c() == 123 // true
assert c.call() == 123 // trueParameters of closures follow the same principle as parameters of regular methods:
- an optional type
- a name
- an optional default value
In addition, parameters are separated by commas.
When a closure does not explicitly define a parameter list (using ->), a closure always defines an implicit parameter, named it.
def greeting = { "Hello, $it!" }
assert greeting('Patrick') == 'Hello, Patrick!'
// The closure above is strictly equivalent to this one:
def greeting = { it -> "Hello, $it!" }
assert greeting('Patrick') == 'Hello, Patrick!'If you want to declare a closure which accepts no argument and must be restricted to calls without arguments, then you must declare it with an explicit empty argument list:
// Declaring a closure with no parameters allowed.
def magicNumber = { -> 42 }
// this call will fail because the closure doesn't accept any argument
magicNumber(11)It is possible for a closure to declare variable arguments like any other method. Varargs methods are methods that can accept a variable number of arguments if the last parameter is of variable length (or is an array):
def concat1 = { String... args -> args.join('') }
assert concat1('abc','def') == 'abcdef'
// The same behavior is directly available if the args parameter is declared as an array
def concat2 = { String[] args -> args.join('') }
assert concat2('abc', 'def') == 'abcdef'
// Again, varargs work as long as the last parameter is an array or an explicit varags type
def multiConcat = { int n, String... args ->
args.join('')*n
}
assert multiConcat(2, 'abc','def') == 'abcdefabcdef'For a function/closure to take a Closure as a parameter, that parameter must be declared as the Closure datatype.1
Example
private defaultResponseHandler = { resp, reader ->
assert resp.status == 200
println "My response handler got response: ${resp.statusLine}"
println "Response length: ${resp.headers.'Content-Length'}"
System.out << reader // print response reader
}
void fetchContent(String path, Closure c = defaultResponseHandler){
connect path, TEXT, c
}In this case, from my HttpClient, I make the Closure parameter optional by giving it a default value. In either case, the closure works as a callback.
To understand the concept of delegates, we must first understand the meaning of this inside a closure. A closure actually defines 3 distinct things:
-
this, which corresponds to the current instance of the enclosing class where the closure is defined -
owner, which corresponds to the enclosing object where the closure is defined, which may be either an instance of class or a closure -
delegate, which corresponds to a third party object where methods calls or properties are resolved whenever the receiver of the message is not defined
In a closure, calling getThisObject will also return the current instance of the enclosing class where the closure is defined. It is equivalent to using an explicit this:
class Enclosing {
void run() {
def whatIsThisObject = { getThisObject() }
// calling the closure will return the instance of Enclosing where the the closure is defined
assert whatIsThisObject() == this // true
// Equivalent to whatIsThisObject above
def whatIsThis = { this }
assert whatIsThis() == this // true
}
}
// If the closure is defined in an inner class,
// "this" returns the inner class, not the top-level one.
class EnclosedInInnerClass {
class Inner {
Closure cl = { this }
}
void run() {
def inner = new Inner()
assert inner.cl() == inner
}
}
// Nested closures
class NestedClosures {
void run() {
def nestedClosures = {
// Here, in the closure named `cl`, `this` corresponds to the closest outer class,
// not the enclosing closure!
def cl = { this }
cl()
}
assert nestedClosures() == this // true: "this" is the instance of the NestedClosures class, not the nestedClosures method.
}
}- In general, you will want to use the shortcut
thisnotation. - If the closure is defined in a inner class,
thisin the closure will return the inner class, not the top-level one. - The code inside the closure can call methods of the enclosing class through
this.class Person { String name int age String toString() { "$name is $age years old" } String dump() { def cl = { String msg = this.toString() println msg msg } cl() } } def p = new Person(name:'Janice', age:74) assert p.dump() == 'Janice is 74 years old'
The owner of a closure is very similar to the definition of this in a closure with a subtle difference: owner will return the direct enclosing object, be it a closure or a class:
class Enclosing {
void run() {
def whatIsOwnerMethod = { getOwner() }
assert whatIsOwnerMethod() == this // true
def whatIsOwner = { owner }
assert whatIsOwner() == this // true
}
}
// Again, as with "this", if the closure is defined in a inner class,
// "owner" in the closure will return the inner class, not the top-level one
class EnclosedInInnerClass {
class Inner {
Closure cl = { owner }
}
void run() {
def inner = new Inner()
assert inner.cl() == inner
}
}
class NestedClosures {
void run() {
def nestedClosures = {
def cl = { owner }
cl()
}
assert nestedClosures() == nestedClosures
}
}- In general, you will want to use the shortcut
ownernotation, instead ofgetOwner(). - Again, as with
this, if the closure is defined in a inner class,ownerin the closure will return the inner class, not the top-level one.