Groovy语言规范中文版之闭包
闭包官方文档:Closures 1.语法 Syntax1.1定义一个闭包 Defining a closure一个闭包的定义遵从一下语法规则: { [closureParameters -> ] statements } [closureParameters->]是一个可选择的逗号间隔的一系列参数,statements是一个或者多个Groovy声明。闭包的参数和方法的参数列表相似,参数可以是明确类型的也可以是不明确的。 当参数列表是指定的,->是必须的,其被当做是参数和闭包体的分隔。声明由0,或多个Groovy声明组成。 下面是一些有效的闭包定义类型: { item++ }//一个指向名为item的变量的闭包 { -> item++ }//显示的间隔code通过使用箭头(->) { println it } //一个使用隐式参数(it)的闭包 { it -> println it }//it是一个显式变量,这种选法是可选的 { name -> println name }//这种写法较好 { String x,int y -> //闭包接受两个明确类型的参数 println "hey ${x} the value is ${y}" } { reader -> // 一个闭包可以接受多行声明 def line = reader.readLine() line.trim() } 1.2.作为对象的闭包 Closures as an object一个闭包是groovy.lang.Closure的实例,可被赋值给一个变量或字段,尽管看起来像一个代码块: def listener = { e -> println "Clicked on $e.source" } //你可以将闭包赋值给一个变量 assert listener instanceof Closure//闭包是groovy.lang.Closure的实例 Closure callback = { println 'Done!' } Closure isTextFile = {//闭包的返回值类型是可选的 File it -> it.name.endsWith('.txt') } 1.3. 调用闭包 Calling a closure闭包,是匿名的代码块,可以像方法一样被调用。如果你定义一个闭包像这样没有参数: def code = { 123 } 然后闭包内的代码将会在调用这个闭包时执行,可以当做一个普通的方法: assert code() == 123 可选的你也可以显示的使用call方法调用: assert code.call() == 123 以上这些规则,对于有参数的闭包也是适用的: def isOdd = { int i-> i%2 == 1 } //定义一个接受int类型作为参数的闭包 assert isOdd(3) == true //可以被直接调用 assert isOdd.call(2) == false //或者使用call方法调用 def isEven = { it%2 == 0 } //使用隐式参数定义一个闭包 assert isEven(3) == false //直接调用 assert isEven.call(2) == true//使用call方法调用 与方法不同的是,闭包在调用时总会返回一个值。下一部分将会讨论怎样声明一个带参数的闭包,以及使用和隐式参数”it” 2.参数 Parameters2.1. 普通参数 Normal parameters闭包的参数与普通方法一样遵从同样的规则:
参数之艰难使用逗号隔开: def closureWithOneArg = { str -> str.toUpperCase() } assert closureWithOneArg('groovy') == 'GROOVY' def closureWithOneArgAndExplicitType = { String str -> str.toUpperCase() } assert closureWithOneArgAndExplicitType('groovy') == 'GROOVY' def closureWithTwoArgs = { a,b -> a+b } assert closureWithTwoArgs(1,2) == 3 def closureWithTwoArgsAndExplicitTypes = { int a,int b -> a+b } assert closureWithTwoArgsAndExplicitTypes(1,2) == 3 def closureWithTwoArgsAndOptionalTypes = { a,int b -> a+b } assert closureWithTwoArgsAndOptionalTypes(1,2) == 3 def closureWithTwoArgAndDefaultValue = { int a,int b=2 -> a+b } assert closureWithTwoArgAndDefaultValue(1) == 3 2.2.隐式参数 Implicit parameter当一个闭包没有显示的定义一个参数列表(使用->),闭包通常使用隐式的参数,名为”it”。代码风格如下: def greeting = { "Hello,$it!" } assert greeting('Patrick') == 'Hello,Patrick!' 与下列代码,严格等价: def greeting = { it -> "Hello,Patrick!' 如果你想声明一个闭包不需要参数,你也应该在调用时不能使用参数,这时需使用无参调用。 def magicNumber = { -> 42 } // this call will fail because the closure doesn't accept any argument调用失败 magicNumber(11) 2.3.可变参数 Varargs闭包也可以像方法一样可以声明可变参数。如果闭包的最后一个参数是可变的,像下面这个类型: def concat1 = { String... args -> args.join('') }//一个接受可变数量字符串作为第一参数的闭包 assert concat1('abc','def') == 'abcdef' def concat2 = { String[] args -> args.join('') }//该闭包可以被调用传入任意数量参数,而无需显式的包裹这个数组 assert concat2('abc','def') == 'abcdef' def multiConcat = { int n,String... args ->//效果同上 args.join('')*n } assert multiConcat(2,'abc','def') == 'abcdefabcdef' 3.委托机制(暂时这么翻译)Delegation strategy3.1.Groovy闭包 vs lambda表达式 Groovy closures vs lambda expressionsGroovy定义闭包作为groovy.lang.Clousure的实例对象出现。与在Java8中出现的lambda表达式不同。关于lambda请参考:Java 8 Lambda表达式探险。Delegation是Groovy闭包的关键概念,这在lambdas中是没有的。闭包改变delegate或者改变delegation strategy的能力使得把Groovy设计成特定领域语言(DSL)成为了可能。 3.2.Owner,delegate,和this理解delegate的概念,我们首先应该解释一下this在闭包内部的含义。闭包内部通常会定义一下3种类型:
网上关于这方面的只是介绍: Groovy闭包深入浅出 3.2.1. this的含义 The meaning of this在闭包中,调用getThisObject将会返回闭包定义处所处的类。等价于使用显示的this: class Enclosing { void run() { def whatIsThisObject = { getThisObject() } //① assert whatIsThisObject() == this //② def whatIsThis = { this } //③ assert whatIsThis() == this //④ } } class EnclosedInInnerClass { class Inner { Closure cl = { this } //⑤ } void run() { def inner = new Inner() assert inner.cl() == inner //⑥ } } class NestedClosures { void run() { def nestedClosures = { def cl = { this } //⑦ cl() } assert nestedClosures() == this //⑧ } } 注:
闭包可能的调用封闭类中的方法方式: class Person { String name int age String toString() { "$name is $age years old" } String dump() { def cl = { String msg = this.toString()//在闭包中使用this调用toString方法,将会调用闭包所在封闭类对象的toString方法,也就是Person的实例 println msg msg } cl() } } def p = new Person(name:'Janice',age:74) assert p.dump() == 'Janice is 74 years old' 3.2.2.闭包中的Owner Owner of closure闭包中的owner和闭包中的this的定义非常的像,只不过有一点微妙的不同:它将返回它最直接的封闭的对象,可以是一个闭包也可以是一个类的: class Enclosing { void run() { def whatIsOwnerMethod = { getOwner() }//① assert whatIsOwnerMethod() == this //② def whatIsOwner = { owner } //③ assert whatIsOwner() == this //④ } } 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 //⑧ } }
3.2.3.闭包中的Delegate Delegate of a closure闭包的delegate可以通过使用闭包的delegate属性或者调用getDelegate方法。这是Groovy构建为领域特定语言的一个有力的概念。closure-this和closure-owner指向的是语义上的闭包范围,而delegate是用户自定义的供闭包使用的对象。默认的,delegate被设置为owner: class Enclosing { void run() { def cl = { getDelegate() } //① def cl2 = { delegate } //② assert cl() == cl2() //③ assert cl() == this //④ def enclosed = { { -> delegate }.call()//⑤ } assert enclosed() == enclosed //⑥ } }
闭包的delegate可以被更改为任意的对象。先定义两个相互之间没有继承关系的类,二者都定义了一个名为name的属性: class Person { String name } class Thing { String name } def p = new Person(name: 'Norman') def t = new Thing(name: 'Teapot') 然后,定义一个闭包通过delegate获取一下name属性: def upperCasedName = { delegate.name.toUpperCase() } 然后,通过改变闭包的delegate,你可以看到目标对象发生了改变: upperCasedName.delegate = p assert upperCasedName() == 'NORMAN' upperCasedName.delegate = t assert upperCasedName() == 'TEAPOT' At this point,the behavior is not different from having a `variable defined in the lexical scope of the closure: def target = p def upperCasedNameUsingVar = { target.name.toUpperCase() } assert upperCasedNameUsingVar() == 'NORMAN' 主要的不同如此:
3.2.4.委托机制(暂时这么翻译)Delegation strategy无论何时,在闭包中,访问一个属性,不需要指定接收对象,这时使用的是delegation strategy: class Person { String name } def p = new Person(name:'Igor') def cl = { name.toUpperCase() } //name不是闭包括号内的一个变量的索引 cl.delegate = p //改变闭包的delegate为Person的实例 assert cl() == 'IGOR'//调用成功 之所以可以这样调用的原因是name属性将会自然而然的被delegate的对象征用。这样很好的解决了闭包内部属性或者方法的调用。不需要显示的设置(delegate.)作为接收者:调用成功是因为默认的闭包的delegation strategy使然。闭包提供了多种策略方案你可以选择: 使用下面的代码来描绘一下”owner first”:
4.GString中的闭包 Closures int GStrings先看一下如下代码: def x = 1 def gs = "x = ${x}" assert gs == 'x = 1' 结果正如你所想象的那样,但是如果哦我们添加如下代码将会发生什么: x = 2 assert gs == 'x = 2' 你将会看到assert失败了!原因有两点:
意思是GString比较懒只会在创建时计算。
在我们的例子中,GString带有一个x的索引。当GString被创建完毕,x的值是1,所以GString被创建且值为1。我们们assert该GString则使用toString转化成String。当我们将x的值更改为2是,我们确实改变了x的值,但是不同于对象,GString仍然指向旧的那个。 如果索引的值改变,GString只会改变他的toString方法所代表值。如果索引发生改变,什么都不会发生。 如果你需要一个真正的闭包在GString中,下面的例子强制使用变量的延迟计算,你需要使用语法${->x}: def x = 1 def gs = "x = ${-> x}" assert gs == 'x = 1' x = 2 assert gs == 'x = 2' 描述一下下面这段代码的变化: class Person { String name String toString() { name }//Person类的toString方法返回name属性 } def sam = new Person(name:'Sam') //创建第一个Person对象名为Sam def lucy = new Person(name:'Lucy') //另一个名为Lucy的对象 def p = sam //变量赋值为sam def gs = "Name: ${p}"//(官方文档在这个地方犯错误了) assert gs == 'Name: Sam' p = lucy assert gs == 'Name: Sam' sam.name = 'Lucy' assert gs == 'Name: Lucy' 所以,如果你希望依赖变化的对象或者封装的对象,你应该使用显式声明无参闭包在GString中。 class Person { String name String toString() { name } } def sam = new Person(name:'Sam') def lucy = new Person(name:'Lucy') def p = sam // Create a GString with lazy evaluation of "p" def gs = "Name: ${-> p}" assert gs == 'Name: Sam' p = lucy assert gs == 'Name: Lucy' 5.闭包强转 Closure coercion闭包可以被转成接口或者单抽象方法的类型。详情请访问用户手册的这部分。 6.函数式编程 Functional programming像Java8中的lambda表达式–闭包,是Groovy函数式编程范式的核心。一些函数式编程关于函数的操作直接适用于Closure类,就像本部分描述的。 6.1 科里化 Curring对于科里化不了解的同学,请参考Lambda演算与科里化(Currying)百度百科:科里化在计算机科学中,柯里化(Currying)是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数且返回结果的新函数的技术。这个技术由 Christopher Strachey 以逻辑学家 Haskell Curry 命名的,尽管它是 Moses Schnfinkel 和 Gottlob Frege 发明的。 进入正题 6.1.1.左科里化 Left curryingLeft currying是先设置闭包最左边的参数,比如下面的例子: def nCopies = { int n,String str -> str*n }//nCopies闭包定义了两个参数 def twice = nCopies.curry(2)//curry将会设置第一个参数为2,并创建了一个新的闭包,且接收单个参数String assert twice('bla') == 'blabla' //所以新的闭包可以接收一个String参数 assert twice('bla') == nCopies(2,'bla') 6.1.2.右科里化 Right currying与左科里化很相似,例子如下: def nCopies = { int n,String str -> str*n } def blah = nCopies.rcurry('bla') assert blah(2) == 'blabla' assert blah(2) == nCopies(2,'bla') 6.1.3. 基于索引的科里化 Index based currying为防止一个闭包超过2个参数,使用任意参数的科里化ncurry成为了可能: def volume = { double l,double w,double h -> l*w*h } def fixedWidthVolume = volume.ncurry(1,2d) assert volume(3d,2d,4d) == fixedWidthVolume(3d,4d) def fixedWidthAndHeight = volume.ncurry(1,4d) assert volume(3d,4d) == fixedWidthAndHeight(3d) 6.2. MemoizationMemoization 允许将调用闭包获得的结果缓存起来。如果调用方法(闭包)进行计算很慢,但是又需要经常调用且使用同样的参数。一个典型的例子就是裴波那契数的计算。一个粗糙(幼稚)的实现,看起来像这样: def fib fib = { long n -> n 6.3. 组成 Composition闭包的组成对应于函数的组成的概念,也就是说创建一个新的函数通过叠加两个或多个函数(链式调用),例如: def plus2 = { it + 2 } def times3 = { it * 3 } def times3plus2 = plus2 6.4.Trampoline递归算法通常收到物理环境的限制:堆栈的最大值。例如,你调用一个递归方法太深,最终会抛出StackOverflowException。 通过使用闭包和它的trampoline能力将是一个不错的方法。 闭包被封装在TrampolineClosure。在调用时,一个trampolined的闭包将会调用原始的闭包并等待结果。如果调用的输出是另一个TrampolineClosure的实例,这个新生成的闭包将作为结果调用trampoline()方法,这个Closure将会被调用。重复的调用并返回trampolined 闭包实例将会持续直到返回一个值而不是一个trampolined Closure。这个值将成为trampoline的最终结果。这种方式的调用连续且不会内存溢出。 下面是一个使用trampoline()来实现阶乘的例子: def factorial factorial = { int n,def accu = 1G -> if (n 6.5.方法指针 Method pointers通常实际应用中会用到规则的方法作为闭包。例如,你可能想要使用闭包的科里化能力,但是科里化对于普通方法是不合适的。在Groovy中,你可以通过方法指针操作符生成一个闭包。 翻译仓促,错误之处还请指出!由于近期较忙,关于闭包的学习总结先告一段落~(编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |