What you'll learn

  • Closure syntax, parameters and the implicit 'it'
  • Calling closures and passing them to methods
  • The trailing-closure syntax
  • How closures capture (and can change) variables
  • Function factories and method pointers (.&)

A closure is a block of code you can store in a variable, pass to a method and run later. Closures are to Groovy what lambdas are to Java — but more flexible, and used everywhere: every each, collect and findAll takes one, and so do Gradle and Jenkins DSLs.

Writing a closure

def greet = { String name -> "Hello, $name!" }
greet('Acme')            // Hello, Acme!
greet.call('Globex')     // the same, explicitly

Parameters go before ->, the body after. The last expression is returned.

The implicit it

A closure with one parameter can skip declaring it — it’s called it:

def shout = { it.toUpperCase() + '!' }
shout('boomi')           // BOOMI!

No parameters, many parameters, defaults

def now = { -> 'it is time' }                    // explicitly zero parameters
def lineTotal = { qty, price, discount = 0 ->
    qty * price * (1 - discount / 100)
}
lineTotal(10, 20)        // 200
lineTotal(10, 20, 15)    // 170.00

Passing closures to methods

def retry(int times, Closure action) {
    for (attempt in 1..times) {
        try {
            return action(attempt)
        } catch (e) {
            println "attempt $attempt failed: ${e.message}"
        }
    }
    'gave up'
}

When the last argument is a closure, you can write it after the parentheses — this “trailing closure” syntax is what makes Groovy DSLs read like plain language:

def result = retry(3) { attempt ->
    if (attempt < 3) throw new RuntimeException('timeout')
    "succeeded on attempt $attempt"
}

Closures capture variables — and can change them

def total = 0
[250, 90, 1200].each { amount -> total += amount }
println total            // 1540

The closure sees total from the surrounding code and updates it. (Java lambdas can’t do this — they require effectively final variables. Groovy closures hold a reference to the variable itself.)

Returning closures: function factories

def multiplier = { factor -> { number -> number * factor } }
def triple = multiplier(3)
triple(7)                // 21

Each returned closure remembers its own factor — the essence of a closure.

Method pointers

The .& operator turns a method into a closure:

def isValid = this.&isValidSku      // a method in the script
['AX-200', 'bad'].findAll(isValid)
def upper = 'x'.&toUpperCase

The full example

// closures.groovy — closures: blocks of code you can store, pass and call.

// A closure literal: { parameters -> body }
def greet = { String name -> "Hello, $name!" }
println greet('Acme')                    // call like a method
println greet.call('Globex')             // or with call()

// One parameter? Use the implicit 'it'
def shout = { it.toUpperCase() + '!' }
println shout('boomi')

// No parameters
def now = { -> 'it is time' }
println now()

// Multiple parameters and default values
def lineTotal = { qty, price, discount = 0 -> qty * price * (1 - discount / 100) }
println lineTotal(10, 20)                // 200
println lineTotal(10, 20, 15)            // 170.00

// Closures as arguments: the last closure can go outside the parentheses
def retry(int times, Closure action) {
    for (attempt in 1..times) {
        try {
            return action(attempt)
        } catch (e) {
            println "  attempt $attempt failed: ${e.message}"
        }
    }
    'gave up'
}
def result = retry(3) { attempt ->
    if (attempt < 3) throw new RuntimeException('timeout')
    "succeeded on attempt $attempt"
}
println result

// Closures capture variables from where they were defined — and CAN change them
def total = 0
[250, 90, 1200].each { amount -> total += amount }
println "Total: $total"                  // 1540

// Returning closures: a function factory
def multiplier = { factor -> { number -> number * factor } }
def triple = multiplier(3)
println triple(7)                        // 21

// Method pointer: turn a method into a closure with .&
def upper = 'x'.&toUpperCase             // bound to the string 'x'
println upper()                          // X
def isEven = { it % 2 == 0 }
println([1, 2, 3, 4].findAll(isEven))    // [2, 4]

Try it yourself

Write a closure timeIt that takes another closure, runs it, prints how many milliseconds it took, and returns its result.

Show a solution
def timeIt = { Closure work ->
    long start = System.currentTimeMillis()
    def result = work()
    println "took ${System.currentTimeMillis() - start} ms"
    result
}
def sum = timeIt { (1L..1_000_000L).sum() }   // longs: the total is too big for an int
println sum   // 500000500000

Key takeaways

  • { params -> body } creates a closure; one parameter can be the implicit it.
  • Pass closures to methods; a last-argument closure can go outside the parentheses.
  • Closures capture surrounding variables and can modify them.
  • Closures can return closures, and .& turns methods into closures.

Example files