談談Groovy閉包


A closure is a function with variables bound to a context or environment in which it executes.

概述###

閉包和元編程是Groovy語言的兩大精髓。Groovy的閉包大大簡化了容器的遍歷,提升了代碼的可擴展性,使代碼更加簡潔優雅。閉包在Groovy編程中幾乎無處不在。

閉包就是一個閉合代碼塊,可以引用傳入的變量。在 “Python使用閉包結合配置自動生成函數” 一文中,講解了閉包的基本概念及如何使用閉包批量生產函數。本文談談Groovy的閉包及應用。

概念###

定義閉包####

閉包在Groovy 的類型是 groovy.lang.Closure , 如下代碼創建了一個使用 closure 來處理 Range [1,2,...,num] 的函數:

def static funcWithClosure(int num, final Closure closure) {
        (1..num).collect { closure(it) }
    }

使用該函數的代碼如下:

println funcWithClosure(5, {x -> x*x})

如果閉包是最后一個參數,還可以寫成:

println funcWithClosure(5) { x -> x * 2 }

閉包與函數的區別####

有童鞋可能疑惑:閉包的形式很像函數,它與函數有什么區別呢?

我們知道函數執行完成后,其內部變量會全部銷毀,但閉包不會。閉包引用的外部變量會一直保存。閉包引用的外部變量具有“累積效應”,而函數沒有。看下面一段代碼:

    def static add(num) {
        def sum = 0
        sum += num
        return sum
    }

    def static addByClosure(init) {
        def addInner = {
            inc ->
                init += inc
                init
        }
        return addInner
    }

        println "one call: ${add(5)}"  // one call: 5
        println "two call: ${add(5)}"  // two call: 5

        def addClosure = addByClosure(0)
        println "one call: ${addClosure(5)}"  // one call: 5
        println "two call: ${addClosure(5)}"  // two call: 10

第一個函數沒有什么特別,進進出出,每次運行得到相同結果。 第二個函數,返回了一個閉包,這個閉包保存了傳入的初始值,並且這個閉包能夠將初始值加上后續傳入給它的參數。划重點: 這里的初始值 init 是函數傳入的參數,當這個參數被閉包引用后,它在函數第一次執行完成后值並沒有被銷毀,而是保存下來。

柯里化####

“函數柯里化(Currying)示例” 一文中講述了函數柯里化的概念及Scala示例。Groovy 也提供了 curry 函數來支持 Curry.

如下所示,計算 sumPower(num, p) = 1^p + 2^p + ... + num^p 。

// sum(n, m) = 1^m + 2^m + ... + n^m
        def sumPower = {
            power, num ->
                def sum = 0
                1.upto(num) {
                    sum += Math.pow(it, power)
                }
                sum
        }
        def sumPower_2 = sumPower.curry(2)
        println "1^2 + 2^2 + 3^2 = ${sumPower_2(3)}"
        println "1^2 + 2^2 + 3^2 + 4^2 = ${sumPower_2(4)}"

        def sumPower_3 = sumPower.curry(3)
        println "1^3 + 2^3 + 3^3 = ${sumPower_3(3)}"
        println "1^3 + 2^3 + 3^3 + 4^3 = ${sumPower_3(4)}"

sumPower.curry(2) 先賦值 power = 2 帶入閉包塊,得到一個閉包:

def sumPower_2Explict = {
            num ->
                def sum = 0
                1.upto(num) {
                    sum += Math.pow(it, 2)
                }
                sum
}

再分別調用 sumPower_2Explict(3) = 14.0 , sumPower_2Explict(4) = 30.0

柯里化使得閉包的威力更加強大了。 它是一個強大的函數工廠,可以批量生產大量有用的函數。

應用###

閉包可以很容易地實現以下功能:

  • 遍歷容器
  • 實現模板方法模式,可用於資源釋放等
  • 代碼的可復用和可擴展
  • 構建 超輕量級框架

遍歷容器####

如下代碼所示,分別創建了一個Map, List 和 Range, 然后使用 each 方法遍歷。一個閉合代碼塊,加上一個遍歷變量,清晰簡單。注意到,如果是一個單循環遍歷,可以直接用 it 表示;如果是 Map 遍歷,使用 key, value 二元組即可。


class GroovyBasics {
    static void main(args) {
        def map = ["me":["name": 'qin', "age": 28], "lover":["name": 'ni', "age": 25]]
        map.each {
            key, value -> println(key+"="+value)
        }

        def alist = [1,3,5,7,9]
        alist.each {
            println(it)
        }
        (1..10).each { println(it) }


        def persons = [new Person(["name": 'qin', "age": 28]), new Person(["name": 'ni', "age": 25])]
        println persons.collect { it.name }
        println persons.find { it.age >=28 }.name
    }
}

再看一段代碼:


        (1..10).groupBy { it % 3 == 0 } .each {
            key, value -> println(key.toString()+"="+value)
        }

將 [1,10]之間的數按照是否被3除盡分組得到如下結果,使用鏈式調用連接的兩個閉包實現,非常簡明。

false=[1, 2, 4, 5, 7, 8, 10]

true=[3, 6, 9]


模板方法模式####

模板方法模式將算法的可變與不可變部分分離出來。 通常遵循如下模式: doCommon1 -> doDiff1 -> ... DoDiff2 -> ... -> DoCommon2 。 Java 實現模板方法模式,通常需要先定義一個抽象類,在抽象類中定義好算法的基本流程,然后定義算法里那些可變的部分,由子類去實現。可參閱:“設計模式之模板方法模式:實現可擴展性設計(Java示例)”

使用閉包可以非常輕松地實現模板方法模式,只要將可變部分定義成 閉包即可。

def static templateMethod(list, common1, diff1, diff2, common2) {
        common1 list
        diff1 list
        diff2 list
        common2 list
}

def common1 = { list -> list.sort() }
def common2 = { println it }
def diff1 = { list -> list.unique() }
def diff2 = { list -> list }

templateMethod([2,6,1,9,8,2,4,5], common1, diff1, diff2, common2)


可復用與可擴展####

可復用與可擴展性的前提是,將規則、流程中的可變與不可變分離出來,不可變代表着可復用部分,可變代表着可擴展的部分。由於閉包能夠很容易地將可變與不可變分離,因此也很有益於實現代碼的可復用與可擴展。

小結###

閉包和元編程是Groovy語言的兩大精髓。本文講解了Groovy閉包的定義、與函數的區別、柯里化及在遍歷容器、實現模板方法模式等應用。使用閉包可提升代碼的可復用和可擴展性,使代碼更加簡潔優雅,對於提升編程能力非常有益處。

參考文獻###



免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM