A closure is an open, anonymous, block of code that can take zero or arguments, always return a value and be assigned to a variable. A closure may reference variables declared in its surrounding scope they can also contain free variables which are defined outside of its surrounding scope. Closures are of type Closure and we can assign a closure to a variable or pass it as a parameter to a method, also closures should be small pieces of code and should not replace a complex function. Below are some examples on what you can use closures for
Closure basics | // create a closure use curly braces def c = { } println c.class.name println c instanceof Closure // class of Closure // closure is really an anonymous function def sayHello = { name -> // name is a argument println "Hello $name" } sayHello("Paul") List nums = [1,2,3,4,5] nums.each({ print "$it " // 'it' keyword is the default name for each element }) println "" nums.each{ n -> print "$n " // or you can name the element, n in this case } println "" def timesTen(num, closure) { closure(num * 10) } timesTen(10, { println "$it "}) timesTen(2) { // you can write above like this as well, if closure is last argument println "$it " } 10.times { print "$it " } println "" // Random is already imported Random random = new Random() 3.times { println random.nextInt() } |
Closure parameters | // implicit parameter (it) def foo = { println it // keyword 'it' is used if no paraeter name is used } foo("Hello") // make sure no parameters are passed def noparams = { -> println "no params" } noparams() // will throw a MissingMethod exception if parameter is passed // multiple parameters def sayHello = { first, last -> println "$first $last" } sayHello("Paul", "Valle") // default values def greet = { String name, String greeting = "Howdy" -> println "$greeting, $name" } greet("Paul", "Hello") greet("Lorraine") // var-arg def concat = { String... args -> // same as Java vargs args.join(' ') } println concat("Hello", "World", "!!!") println concat("What", "a", "nice", "day", "it is", "today") // Passing closures to methods def someMethod(Closure c){ println "..." println c.maximumNumberOfParameters println c.parameterTypes } def someClosure = { int x, int y -> x + y} someMethod(someClosure) |
I covered collections in my last section, a number of collection have methods that take a closure as a argument, here are some examples
Collection methods | // each & eachWithIndex List favNums = [1,2,3,4,5,6,7,8,9] for (num in favNums){ print "$num " } println() favNums.each {print "$it "} // each can take a closure println() favNums.eachWithIndex{ int entry, int i -> print "$i:$entry "} // eachWithIndex can also take a closure println() // findAll List days = ["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"] List weekend = days.findAll {it.startsWith("S")} print weekend println() // collect List nums = [2,5,7,3,8,2,2,1,0,9] List numsTimesTen = [] // -bad way nums.each {num -> numsTimesTen << num * 10 } println numsTimesTen // -- better way List betterTimesTen = nums.collect {num -> num * 10} println betterTimesTen |
Currying in Groovy will let you set the value of one parameter of a closure, and it will return a new closure accepting one less argument, think of it as create a clone of a closure and fixing values for some of the parameters, this can be done multiple times as well, here are some examples
Curry method examples | // closure with 3 parameters def log = { String type, Date createdOn, String msg -> println "$createdOn [$type] - $msg" } log("DEBUG", new Date(), "This is my first debug statement") // inoke closure passing 3 arguments // create a closure using another closure def debugLog = log.curry("DEBUG") // create a new closure but pass 1 argument, this will now be fixed debugLog(new Date(), "This is another debug statement...") // so we only need to invoke with 2 arguments debugLog(new Date(), "This is one more.....") def todayDebugLog = debugLog.curry(new Date()) // we can continue creating closures fixing parameters todayDebugLog("This is today's debug msg") // thus we only need to pass 1 // right curry def crazyPersonLog = log.rcurry("Why am I logging this way, right curry (rcurry)") crazyPersonLog("ERROR",new Date()) // index based currying def typeMsgLog = log.ncurry(1,new Date()) typeMsgLog("ERROR","This is using ncurry...") |
There are three scopes inside a closure
A delegate means you can delegate an action to something else but you have the option to either use the delegate first (DELEGATE_FIRST) or not depending on what opions you use.
Scoping | class ScopeDemo { def outerClosure = { println this.class.name println owner.class.name println delegate.class.name println "--------------------------" // Nested closure def nestedClosure = { println this.class.name // enclosing class println owner.class.name // points to outer closure println delegate.class.name // points to outer closure } nestedClosure() } } def demo = new ScopeDemo() demo.outerClosure() Output ------------------------------------------------------------------ uk.co.datadisk.closures.ScopeDemo uk.co.datadisk.closures.ScopeDemo uk.co.datadisk.closures.ScopeDemo -------------------------- uk.co.datadisk.closures.ScopeDemo uk.co.datadisk.closures.ScopeDemo$_closure1 uk.co.datadisk.closures.ScopeDemo$_closure1 |
Delegates | def writer = { append 'Paul ' append 'lives in Milton Keynes' } // resolves first in scope first (owner first by default) // override it with the resolveStrategy below def append(String s){ println "append() called with argument of $s" } // now delegating append to to StringBuffer StringBuffer sb = new StringBuffer() writer.resolveStrategy = Closure.DELEGATE_FIRST // override the default (append above) writer.delegate = sb // the delegate will use the append() method in StringBuffer println writer() |