[TOC]
闭包 闭包是一种可执行的代码块的方法,闭包也是对象,可以想方法一样传递参数。由于闭包是代码块,因此也可以在需要时执行。定义闭包的时候可以使用一个或者多个参数。闭包可以访问属性信息,意味着可以访问并修其作用于内的所有变量值。
闭包 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 def greeting = 'hello' def clos = { param -> println "$greeting $param" } clos.call("world" ) greeting = 'welcome' clos.call('world' ) def demo(clos){ def greeting = 'Bonjour' clos.call('ken' ) } demo(clos)
以上代码说明,只有闭包被定义且存在,而不是在被调用时,可以访问其状态值。
闭包也常用在集合中。使用闭包可以更高效的遍历元素。
1 2 3 4 5 6 7 1. upto(10 ,{ println it }) 1. upto(10 ){ println it }
1 2 3 4 5 6 7 8 def fac = 1 1. upto(5 ){ num -> fac *= num } println "Factorical : $fac"
闭包与集合和字符串 常用each方法来遍历。
find()返回第一个满足条件的元素,findAll()返回所有满足条件的元素
1 2 3 4 5 6 def list = [1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 ]list.findAll { it > 5 }.each { println it }
any() and every()
1 2 3 4 5 6 7 8 9 10 11 12 def list = [1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 ]def anyEle = list.any { it > 5 } println anyEle def everyEle = list.every { it > 5 } println everyEle
collect() and inject
collect用来遍历集合并且返回一个集合
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 def list = [1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 ]def newlist = list.collect { it * it } println newlist def doubles = { item -> 2 * item } def triples = { item -> 3 * item } def isEven = { item -> item%2 == 0 } def map(clos,list){ return list.collect(clos) }
inject用来遍历集合,首先将需要传递的值和集合项目传递给必报,此时其传递的值将作为处理结果,然后再和下一个集合项目一起传递给闭包,以此类推
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 def factorial = [2 , 3 , 4 , 5 ] .inject(1 ) { previous, element -> previous * element } println factorial def list = [2 , 3 , 4 , 5 ]factorial = list.inject(1 ) { prviouse, element -> prviouse * element } println factorial def list = [2 , 3 , 4 , 5 ]def closure = { previous, element -> previous * element } def factorial = list.inject (1 ,closure)println factorial
闭包的其他特性 由于闭包也是一个对象,因此它可以作为方法的参数,同时也可以作为另一个闭包的参数。
作为方法的参数 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 def filter(list, predict) { return list.findAll(predict) } def isEven = { x -> return x % 2 == 0 } def isOdd = { x -> return !isEven(x) } def table = [11 , 12 , 13 , 14 ]def evens = filter(table,isEven)println evens def odds = filter(table,isOdd)println odds
作为另一个闭包的参数 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 def takewhile = { predicate, list -> def result = [] for (element in list) { if (predicate(element)) { result << element } else { return result } } } def isEven = { x -> return x % 2 == 0 } def isOdd = { x -> return !isEven(x) } def table1 = [12 , 14 , 15 , 18 ]def table2 = [11 , 13 , 15 , 16 , 18 ]def evens = takewhile.call(isEven,table1)println evens
作为返回值 1 2 3 4 5 6 7 8 9 10 def multiply(x){ return { y-> return x * y } } def twice = multiply(2 )println twice(4 )
嵌套闭包 选择排序算法的实现 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 def selection = { list -> def swap = { sList, p, q -> def temp = sList[p] sList[p] = sList[q] sList[q] = temp } def minimumPosition = { pList, from -> def mPos = from def nextFrom = 1 + from for (j in nextFrom..<pList.size) { if (pList[j] < pList[mPos]) { mPos = j } } return mPos } def size = list.size() - 1 for (k in 0. .<size) { def minPos = minimumPosition(list, k) swap(list, minPos, k) } return list } def table = [13 , 14 , 12 , 11 , 14 ]def sorted = selection(table)println sorted
关于闭包的更多信息 闭包和不确定性 先看一个例子,闭包在实参列表外部。如果方法的最后一个参数是闭包,那么可以将它从实参列表中删除,并放在气候的括号后面。
demo(closure) 变为 demo closure
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 def greeting = 'Hello' def clo = { param -> println "$greeting $param" } def demo(clos) { def greeting = 'Bonjour' clos.call('Ken' ) } demo clo demo() { param -> println "welcome $param" }
假设clos是一个闭包变量,{...}是一个闭包体。假设x和y是两个随意设置的值,则查看如下不同的方法调用语句:
1 2 3 4 method(x,y,{...}) method(x,y){...} method(x,y,clos) method(x,y)clos
第二行代码把闭包体直接放在调用参数的后面。第四行代码说明,同样的做法也许不适用闭包变量。Groovy解释器无法忍冬clos标识符是该方法的调用的一部分,因此Groovy解释器就会报错,说明找不到只有具有两个参数的method方法。
如果method标识具有一个参数的方法:
则clos是个闭包变量,{…}是闭包体,如下调用语句
1 2 3 4 5 6 method({...}) method(){...} method{...} method(clos) method()clos method clos
第五行代码中,调用方法时不适用任何实参,在该方法体中,形参将被初始化为null,在这个方法中使用闭包变量将会导致系统错误。
闭包和方法 对于如下两个结构
1 2 3 4 5 6 7 8 def mDouble = { n -> return 2 *n } def mDouble(n){ return 2 *n }
前者是一个闭包定义,后者是一个方法定义。注意,任何闭包引用肯定有固定的作用域。不能出现同名的闭包变量,但是可以出现同名方法(方法重载)
默认参数 像方法一样,闭包也可以被分配带默认值的参数。
1 2 3 4 5 6 7 def greeting = { msg, name = "leo" -> println "$msg $name" } greeting("hello " ) greeting("hello" , "julia" )
闭包和作用域 上面讲到的局部闭包实现的选择性排序算法。下面讲一个使用冒泡算法排序。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 def bubbleSort = { list -> def swap = { sList, p, q -> def temp temp = sList[q] sList[q] = sList[p] sList[p] = temp } def size = list.size() def sortedPosition = 0 while (sortedPosition < size) { for (index in 1. .<(size - sortedPosition)) { if (list[index] < list[index - 1 ]) swap(list, index, index - 1 ) } sortedPosition ++ } return list } def list = [1 ,5 ,4 ,6 ,2 ,7 ,0 ,78 ,54 ,23 ]bubbleSort(list) println list
需要注意的是,闭包只能由语句组成,这意味着闭包中不能有方法定义,因此局部闭包swap不能使用方法定义来替代。
递归闭包 用递归的方法求n!
1 2 3 4 5 6 def factorial = { n -> return n == 0 ? 1 : n * call(n - 1 ) } println factorial(5 )
状态类型 可以使用参数和返回值的动态类型来定义闭包。这个特性使得闭包更加通用。
闭包的动态类型 1 2 3 4 5 6 7 8 9 10 11 12 13 def times = { x, y -> return x * y } def times = { x, y -> return x * y } println times(3 ,4 ) println times(3.2 ,4.15 ) println times('hello' ,4 )
在调用时,才确定x和y的类型
1 2 3 12 13.280 hellohellohellohello
静态指定参数类型的闭包 1 2 3 4 def times = { Number x, Number y -> return x * y }
这种方式就限定了闭包的参数类型,不能是Number及其子类意外的其他类型。
有关实参的约定 在调用闭包时,实参的数量必须严格匹配闭包定义时的形参数量。否则,Groovy解释器就会报错,指示参数不正确。
1 2 3 4 5 6 7 8 def clos = { a,b,c -> "clos($a$b$c)" } clos(1 ,2 ,3 ) clos(1 ) clos(1 ,2 ,3 ,4 )
闭包、集合、范围 列表、映射、和范围等类型提供很多实用闭包作为参数的方法,这有利于迭代处理集合或者范围中的每个元素,并执行指定的任务。
迭代器方法
方法名称
说明
any*
collect*
collect*
each*
every*
find*
findAll*
findIndexOf*
inject*
reverseEach*
sort*
Return 语句 我们先看一个代码范例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 def isMemberA(item, list) { def size = list.size() for (index in 0. .<size) { if (list[index] == item) return true } return false } def isMemberB(item, list) { list.each { if (it == item) return true } return false } def numbers = [11 , 12 , 13 , 14 ]println isMemberA(15 ,numbers) println isMemberA(13 ,numbers) println isMemberB(15 ,numbers) println isMemberB(13 ,numbers)
输出为:
显然,Groovy方式的返回是有问题的。这个是为什么呢?
在我们的范例中,作为实参的闭包包含一个return语句。闭包返回true,就会导致while循环继续处理列表中的下一个项目。
可以看一下each的实现方式
1 2 3 4 5 6 7 8 public static <T> Iterator<T> each(Iterator<T> self, @ClosureParams (FirstGenericType.class ) Closure closure) { while (self.hasNext()) { Object arg = self.next(); closure.call(arg); } return self; }
可以看到return是指返回的closure.call()但是外层循环还是在的,并不能直接终止循环,因此最终所有的数都会跑一边,这个是一个比较需要注意的地方,在使用each进行迭代的时候一定要注意哦!
高级闭包
假设某家超时使用特殊的商品价格来吸引新客户,以及维护已经有的客户。在一周内,也许早餐谷类视频的价格优惠1/3;在另一周内,优惠政策可能变化,化妆品销售按照买一送一的方式进行销售。在这种优惠政策经常变化的环境中,需要某种灵活的方式来定义这些“业务规则”,并且能够灵活的修改和调整。
此处的思路来源多范式编程 方式,这是Groovy的一个非常重要的特性。借助更高级的函数,函数编程范式综合了多台、组合以及计算模式。
简单闭包 以前,把闭包描述为一个代码块。闭包实现参数化更加有价值,可以被引用,可以被当做参数传入方法,以及可以与call消息一起被调用。
范例:计算两个数值参数的乘积
1 2 3 4 5 6 7 def multiply = { x, y -> return x * y } println "multiply(3,4) = ${multiply(3,4)}" println "multiply(3.4,5.6) = ${multiply(3.4,5.6)}"
由于闭包可以引用对象的状态。
范例:作用域和闭包
1 2 3 4 5 6 7 8 9 10 11 12 def multiplier = 2 def multiply = { x -> return x * multiplier } println "multiply(3) = ${multiply(3)}" println "multiply(5.6) = ${multiply(5.6)}" multiplier = 3 println "multiply(3) = ${multiply(3)}" println "multiply(5.6) = ${multiply(5.6)}"
在闭包定义范围内的变量可以从闭包代码内访问。
闭包是可以将另一个闭包返回的,类似一个二阶的函数
范例:闭包返回值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 def add = { x, y -> return x + y } def subtract = { x, y -> return x - y } def multiply = { x, y -> return x * y } def divide = { x, y -> return x / y } def arithmetic = { arith -> switch (arith) { case 'ADD' : return add case 'SUBTRACT' : return subtract case 'MULTIPLY' : return multiply case 'DIVIDE' : return divide default: return add } } def addOperation = arithmetic('ADD' )def mulOperation = arithmetic('MULTIPLY' )println "addOperation(3,4) = ${addOperation(3,4)}" println "mulOperation(3,4) = ${mulOperation(3,4)}" println arithmetic('DIVIDE' )(3 ,4 )
部分应用 在上面的范例2中,multiply闭包计算一个参数和内部变量multiplier的乘积。现在,如果把multiplier作为闭包的参数,这个闭包返回multiply闭包,后者计算自己的参数与multiplier的成绩,这就是闭包部分应用的范例。
范例:partial applicaiton
1 2 3 4 5 6 7 8 9 10 11 12 def multiply = { x, y -> return x * y } def tripple = multiply.curry(3 )def quadroup = multiply.curry(4 )def twelve = multiply.curry(3 ,4 )println tripple(3 ) println quadroup(5 ) println twelve()
**注意:表达式multiply.curry(3)**表示把参数乘以3的闭包。然后,被返回的闭包使用一个参数进行调用。在数学家Haskell Curry之后,闭包的部分应用被称为修正。闭包triple的定义如下:
1 2 3 4 def triple = { y -> return 3 * y }
其中,第一个参数被删除,所有出现第一个参数的地方使用数字3来替代。
加法和惩罚被称为可交换操作。这意味着A+B = B+A and A*B = B*A。但是减法和除法是不可交换的操作。我们可以事件类似于multiply的闭包。
范例:实现可交换操作
1 2 3 4 5 6 7 8 9 10 11 12 13 def rSubtract = { y, x -> return x - y } def lSubtract = { x, y -> return x - y } def subtract10 = rSubtract.curry(10 )def subractFrom20 = lSubtract.curry(20 )println subtract10(22 ) println subractFrom20(14 )
关于修正闭包的一个重点是:curry方法的实参数量绝对不能超过该必报所需的实参实际数量。比如该闭包有有三个参数,则调用curry方法时,可以使用零个,一个,两个,或者三个实参。
部分应用可以被认为是一种简化形式,是个复杂任务可以被分解为多个子任务。
组合 结构化顺序的一种组织方式是顺序执行多个任务。通常而言,每部分任务被分别设计和实现。在此,也许认为,一个闭包标识需要执行的某个简单任务。适用组合概念进行任务合并,可以非常容易构造出复杂的任务。并且,借助不同的祝贺方式,可以较快的创建超时所需要的新任务,本文开篇所述案例。
范例:闭包组合
范例演示了composition闭包,这个闭包有两个参数f和g,两个参数代表闭包,把闭包g应用到x比如g(x),把闭包f应用到处理结果比如(fg(x))
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 def composition = { f, g, x -> println "f:${f.class} g: ${g.class} x: ${x.class}" return f(g(x)) } def composition2 = { f, g, x -> return f(x) * g(x) } def multiply = { x, y -> return x * y } def triple = multiply.curry(3 )def quadroup = multiply.curry(4 )def twelveTimes = composition.curry(triple, quadroup)def multiplyCompo = composition2.curry(triple,quadroup)println "twelveTimes(12) = ${twelveTimes(12)}" println "multiplyCompo(12) = ${multiplyCompo(12)}"
闭包triple有一个参数,并把这个参数与3相乘。闭包twelveTimes是闭包tripe和quadroup的组合。结果是,闭包twelveTimes把参数乘以4,然后把乘法结果与3相乘。
范例:组合与集合
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 def composition = { f, g, x -> return f(g(x)) } def multiply = { x, y -> return x * y } def triple = multiply.curry(3 )def quadroup = multiply.curry(4 )def twelveTimes = composition.curry(triple,quadroup)def table = [1 ,2 ,3 ,4 ].collect{ element -> return twelveTimes(element) } println "tabl = $table "
计算模式
介绍一种机制,是的闭包包含一种计算模式。
范例是对列表中的每个元素按照某中形式进行转换。这种转换通常被命名为map。
范例:映射
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 def map = { clos, list -> return list.collect(clos) } def compositon = { f ,g ,x -> return f(g(x)) } def multiply = { x , y -> return x * y } def triple = multiply.curry(3 )def quadroup = multiply.curry(4 )def tripleAll = map.curry(triple)def table = tripleAll([1 ,2 ,3 ,4 ])println table
分析:
1 2 3 4 5 6 7 8 9 quadroup = { list -> return list.collect{ element -> return 3 * element } }
关于映射的一个等级模式是,如果把闭包(比如f)映射到列表x,然后把闭包(比如g)应用到前一个闭包的处理结果。那么上述过程就等价于把g和f的组合应用到这个列表x。
范例:处理等价关系
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 def map = { clos, list -> return list.collect(clos) } def compositon = { f, g, x -> return f(g(x)) } def multiply = { x, y -> return x * y } def triple = multiply.curry(3 )def quadroup = multiply.curry(4 )def composeMapMap = compositon.curry(map.curry(triple), map.curry(quadroup))def table = composeMapMap([1 ,2 ,3 ,4 ]) println table
业务规则
现在我们考虑如何计算一个Book的净价,在计算过程要考虑到上品折扣和政府税金,比如增值税。
关于业务规则的实际实现
范例:净价计算
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 class Book { def name def author def price def category } def bk = new Book(name: 'Groovy' , author: 'KenB' , price: 25 , category: 'CompSci' ) def discountRate = 0.1 def taxRate = 0.17 def rMultiply = { y , x -> return x * y } def lMultiply = { x , y -> return x * y } def composition = { f,g,x -> return f(g(x)) } def calDiscounteedPrice = rMultiply.curry(1 -discountRate)def calTax = rMultiply.curry(1 + taxRate)def calcNetPrice = composition.curry(calTax,calDiscounteedPrice)def netPrice = calcNetPrice(bk.price)println "netPrice = ${netPrice}"
范例:有上限折扣
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 class Book { def name def author def price def category } def bk = new Book(name: 'Groovy' , author: 'KenB' , price: 25 , category: 'CompSci' )def discountRate = 0.1 def taxRate = 0.17 def maxDiscount = 3 def rMultiply = { y, x -> return x * y } def lMultiply = { x, y -> return x * y } def subtract = { x, y -> return x - y } def rSubtract = { y, x -> return x - y } def lSubtract = { x, y -> return x - y } def min = { x, y -> return x < y ? x : y } def id = { x -> return x } def composition = { f, g, x -> return f(g(x)) } def bComposition = { h, f, g, x -> return h(f(x), g(x)) } def calcDiscount = rMultiply.curry(discountRate)def calcActuralDiscount = bComposition.curry(min, calcDiscount, id)def calcDiscountedPrice = bComposition.curry(subtract, id, calcActuralDiscount)def calcTax = rMultiply.curry(1 + taxRate)def calcNetPrice = composition.curry(calcTax,calcDiscountedPrice)println "bk.price : $bk.price" def netPrice = calcNetPrice(bk.price)println "netprice = $netPrice"
打包 以上范例开发了大量非常有价值的闭包,可以非常灵活的合并使用。因此,有必要把这些闭包都打包到一个类中去,这样更加容易引入到应用程序。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 abstract class Functor { public static Closure bAdd = { x, y -> return x + y } public static Closure rAdd = { y, x -> return x + y } public static Closure bSubtract = { x , y -> return x - y } //blabla }
这个类包含了算数的闭包。
闭包与协程 调用一个函数或方法会在程序的执行序列创建一个新的作用域.我们会在一个入口点进入函数.在方法完成之后,回到调用者的作用域.
协程(Coroutine)则支持多个入口点,每个入口点都是上次挂起调用的位置.我们可以进入一个函数,执行部分代码,挂起,在回到调用者的上下文或作用域内执行一些代码.之后我们可以在挂起的地方恢复该函数的执行.
写成对于实现某些特殊的逻辑或算法非常方便,比如用着生产者-消费者问题中.生产者会接受一些输入,对输入做一些厨师处理,通知消费者拿走处理过的值并做进一步计算,并输出或存出结果.消费者处理它那部分工作,完成之后通知生产者以获取更多输入.
在Java中,wait() and notify()与多线程借个使用,可以实现协程.闭包会让人产生”协程是在一个线程中执行”的感觉or错觉.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 def iterate(n, closure){ 1. upto(n){ println "In iterate with value ${it}" closure(it) } } println "Calling iterate" total = 0 iterate(4 ){ total += it println "In closure total so far is ${total}" } println "Done"
协程不是进程或线程,其执行过程更类似于子例程,或者说不带返回值的函数调用 。 协程还是比较复杂,嗯后续在研究研究.
闭包委托 Groovy的闭包支持方法委托,而且提供了方法分派能力.
this,owner and delegate是闭包的三个属性,用于确定有那个对象处理闭包内的方法调用,一般而言,delegate 会被设置为 owner,但是对其进行修改,可以有非常好的元编程能力.
利用闭包委托,可以更好的写出DSL,将在DSL好好的讲述本内容.
题外话 有关函数式编程,可参考阮一峰老师的博客,在这里引用阮一峰老师的博客,特此注明,并非常感谢老师能够出一片教程给大家科普一下。
函数式编程入门教程–阮一峰
ps:自己的一些理解
对于函数式编程中的柯里化,对应高中数学的只是,其实就是函数的一个组合。为了组合函数,需要将多个多参数函数转换成单参数函数。
举个🌰: $$ z = x + y $$
$$ z = x * y $$
如果我想得到一个复合函数使得2->映射到8,计算过程为2 * 3 + 2
那么我怎么得到这个函数呢?定义这个映射为f $$ f(x) = x * 3 + 2 $$ 那么在程序中我们怎么体现呢
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 def add = { x, y -> return x + y } def multiple = { x, y -> return x * y } def compose = { f, g, x -> return f(g(x)) } def add2 = add.curry(2 )def multiple3 = multiple.curry(3 )def addAndMultiple = compose.curry(add2,multiple3)println addAndMultiple(2 )