概述
Groovy 闭包
- 前言
- 1 闭包语法
- 1.1 闭包定义
- 1.2 闭包作为一个对象
- 1.3 闭包调用
- 2 闭包参数
- 2.1常规参数
- 2.2 隐含参数
- 2.3 可变参数
- 3 代理策略
- 3.1 Groovy 闭包VS lambda表达式
- 3.2 owner,delegate和 this
- 3.2.1 闭包中this的含义
- 3.2.2 闭包中的owner
- 3.2.3 闭包中的delegate
- 3.2.4 代理策略
- 4 GString 中的闭包
前言
Groovy闭包对于之前没有多少动态语言学习经验的小伙伴可能刚开始会有点蒙圈,我在Groovy 官网上看到了一个很好的教程,在此记录一下我的学习笔记。
1 闭包语法
1.1 闭包定义
闭包遵守下列语法规则:{ [closureParameters -> ] statements }
;[closureParameters -> ]
表示闭包的参数列表,闭包参数可以是有类型的,也可以是没有类型的,可以是多个也可以没有;“->”符号,用于将参数与闭包体分开;statements
表示0条或者更多的groovy语句。
下面举几个闭包的例子
//例1 闭包引用外部变量
//例1 闭包引用外部变量
int item = 0
def ex1 = { item++ }
//例2 ->分隔开参数与groovy语句,定义了一个无参闭包
def ex2 = { -> item++ }
//例3 闭包使用隐含参数
def ex3 = { println it }
//例4 一个例3的替代版本,it是显示参数
def ex4 = { it -> println it }
//例5 定义一个显示的name参数
def ex5 = { name -> println name }
//例6 定义又类型的多参数闭包
def ex6 = { String x, int y ->
println "hey ${x} the value is ${y}"
}
//例7 一个闭包包含多条语句
def ex7 = { reader ->
def line = reader.readLine()
line.trim()
}
1.2 闭包作为一个对象
闭包作为一个对象,是groovy.lang.Closure类的实例;能够赋值给一个变量,也可以定义成类的域
//例1 定义闭包变量赋值给一个变量,闭包是groovy.lang.Closure的实例
def listener = { e -> println "Clicked on $e.source" }
assert listener instanceof Closure
//例2 能够用groovy.lang.Closure定义闭包变量
Closure callback = { println 'Done!' }
// 例3 闭包可以有返回值类型
Closure<Boolean> isTextFile = {
File it -> it.name.endsWith('.txt')
}
1.3 闭包调用
闭包可以像函数一样调用,也可以显示的调用call()函数
def code = { 123 }
code()
code.call()
与函数不同的是,没有闭包都有返回值,闭包最后一条执行的语句作为返回值。
2 闭包参数
2.1常规参数
闭包参数值遵循常规函数的参数原则:
- 可以定义参数类型,也可以不定义参数类型
- 参数一定要有名字
- 参数可以有默认值
- 多个参数用“,”隔开
2.2 隐含参数
当闭包没有显示的定义一个参数列表时(没有使用->),闭包会定义一个隐式的定义一个参数it
def greeting = { "Hello, $it!" }
def greeting = { it -> "Hello, $it!" }
两者是等价的,如果想定义一个无参的闭包就必须使用“->”
def magicNumber = { -> 42 }
2.3 可变参数
闭包可以定义可变参数
//例1 定义可变参数闭包
def concat1 = { String... args -> args.join('') }
//例2 调用可变参数闭包不需要用数组包裹
assert concat1('abc','def') == 'abcdef'
//例3 用数组定义可变参数闭包
def concat2 = { String[] args -> args.join('') }
assert concat2('abc', 'def') == 'abcdef'
//例4 只有最后一个参数才能定义成一个可变参数
def multiConcat = { int n, String... args ->
args.join('')*n
}
assert multiConcat(2, 'abc','def') == 'abcdefabcdef'
3 代理策略
3.1 Groovy 闭包VS lambda表达式
groovy 闭包是 Closure 类和Java lambda表达式有很大区别,具体请参考 Java Lambda文档
3.2 owner,delegate和 this
为了理解delegate概念,我们必须先理解闭包中的this;实际上闭包定义了3个不同的概念:
- this 对应于定义闭包的封闭类(封闭类:包含成员对象的类叫封闭类(Enclosing class))
- owner 对应于定义闭包的封闭对象,可以是类也可以是闭包
- delegate 是一个第三方对象
3.2.1 闭包中this的含义
在闭包中调用getThisObject函数将会返回定义闭包的封闭类对象,相当于显示的使用this关键字
class Enclosing {
void run() {
def whatIsThisObject = { getThisObject() } //在一个封闭类中定义一个闭包并通过getThisObject 函数返回
assert whatIsThisObject() == this //调用闭包看是不是返回的就是封闭类
def whatIsThis = { this } //定义闭包,返回this
assert whatIsThis() == this //判断this是不是封闭类对象
}
}
class EnclosedInInnerClass {
class Inner {
Closure cl = { this } //在内部类中定义一个闭包
}
void run() {
def inner = new Inner()
assert inner.cl() == inner //判断this表示的是内部类,而不是顶部的类
}
}
class NestedClosures {
void run() {
def nestedClosures = { //在闭包内定义一个闭包
def cl = { this }
cl()
}
assert nestedClosures() == this //判断this返回的是不是封闭类,而不是闭包
}
}
class Person {
String name
int age
String toString() { "$name is $age years old" }
String dump() {
def cl = {
String msg = this.toString() //通过this调用封闭类方法
println msg
msg
}
cl()
}
}
def p = new Person(name:'Janice', age:74)
assert p.dump() == 'Janice is 74 years old'
结论:闭包中的this关键字指向的是定义闭包的封闭类,this 关键字指向的不会是闭包,一定是封闭类;可以通过this关键字调用封闭类中的方法 。
3.2.2 闭包中的owner
owner的含义和this非常的相似,但是owner指向的是直接定义闭包的对象,可以是封闭类,也可以是闭包。
class Enclosing {
void run() {
def whatIsOwnerMethod = { getOwner() } // 在封闭类中定义闭包,并返回owner对象
assert whatIsOwnerMethod() == this // 判断闭包owner对象是不是定义闭包的封闭类
def whatIsOwner = { owner } // 定义闭包,通过owner 字段返回对象
assert whatIsOwner() == this // 判断owner是不是封闭类
}
}
class EnclosedInInnerClass {
class Inner {
Closure cl = { owner } // 在内部类中定义闭包,返回owner
}
void run() {
def inner = new Inner()
assert inner.cl() == inner // 判断owner是不是内部类
}
}
class NestedClosures {
void run() {
def nestedClosures = {
def cl = { owner } // 再闭包中定义闭包
cl()
}
assert nestedClosures() == nestedClosures // 判断闭包的owner是不是指向外边包裹的闭包
}
}
结论:owner关键字指向的是定义闭包的对象,可以是封闭类,也可以是闭包;和this的关键区别在于owner可以指向闭包
3.2.3 闭包中的delegate
闭包中的delegate可以直接使用delegate关键字或者getDelegate函数调用;delegate是用户定义闭包来使用的对象,默认delegate被设置成owner
class Enclosing {
void run() {
def cl = { getDelegate() } //定义闭包,通过getDelegate获取delegate对象
def cl2 = { delegate } //定义闭包,通过delegate关键字返回delegate对象
assert cl() == cl2()
assert cl() == this
def enclosed = {
{ -> delegate }.call() // 在闭包中定义一个闭包,返回delegate对象
}
assert enclosed() == enclosed //判断delegate是不是owner
}
}
delegate对象可以设置成任何对象,下面我们通过两个非继承类的例子来验证一下。
class Person {
String name
}
class Thing {
String name
}
def p = new Person(name: 'Norman')
def t = new Thing(name: 'Teapot')
def upperCasedName = { delegate.name.toUpperCase() }
upperCasedName.delegate = p
assert upperCasedName() == 'NORMAN'
upperCasedName.delegate = t
assert upperCasedName() == 'TEAPOT'
上面的例子和下面这个类似
def target = p
def upperCasedNameUsingVar = { target.name.toUpperCase() }
assert upperCasedNameUsingVar() == 'NORMAN'
但是有几个主要的不同
- target 是一个局部变量在闭包中的引用,而delegate不是
- delegate的函数调用不需要显示的使用delegate关键字(下节解释)
3.2.4 代理策略
无论身时候,闭包中的属性被调用,但是没有显示的设置接收对象,是因为代理策略的参与
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对象获取到了name;并不需要显示的吧delegate设置成接收对象;这是闭包的delegate策略帮助完成。闭包定义了多种策略可以选择
- Closure.OWNER_FIRST 这是默认策略;当方法或者属性存在于owner中,则会调用owner对象中的,如果没有,然后调用delegate中的
- Closure.DELEGATE_FIRST 与Closure.OWNER_FIRST策略正好相反
- Closure.OWNER_ONLY 属性或者方法的解析只会查找owner对象,delegate将会忽略
- Closure.DELEGATE_ONLY 属性或者方法的解析只会查找delegate,owner将会被忽略
- Closure.TO_SELF 这个需要开发者自己定制解析策略。
class Person {
String name
int age
def fetchAge = { age }
}
class Thing {
String name
}
def p = new Person(name:'Jessica', age:42)
def t = new Thing(name:'Printer')
def cl = p.fetchAge
cl.delegate = p
assert cl() == 42
cl.delegate = t
assert cl() == 42
cl.resolveStrategy = Closure.DELEGATE_ONLY
cl.delegate = p
assert cl() == 42
cl.delegate = t
try {
cl()
assert false
} catch (MissingPropertyException ex) {
// "age" is not defined on the delegate
}
4 GString 中的闭包
首先看一个例子:
def x = 1
def gs = "x = ${x}"
assert gs == 'x = 1'
x = 2
assert gs == 'x = 2'
第一个assert能通过,第二个assert不能;主要有两点原因:
- GString 对toString是懒赋值策略
- 对于
${x}
在GString创建时,表示的不是闭包,而是表达式$x
在这个例子中,当GString被创建时,x是1,所以GString被创建的时候值是1,放入的是x的值,不是x变量的地址;当x的值变为2时,GString不会一起变化,因为GString引用的不是x 变量,而是x的值,所以不会一起变化。
在GString真正定义一个闭包,需要加上“->”
def x = 1
def gs = "x = ${-> x}"
assert gs == 'x = 1'
x = 2
assert gs == 'x = 2'
再举一个例子:
class Person {
String name
String toString() { name }
}
def sam = new Person(name:'Sam')
def lucy = new Person(name:'Lucy')
def p = sam
def gs = "Name: ${p}"
assert gs == 'Name: Sam'
p = lucy
assert gs == 'Name: Sam' //这里不会报错是因为在创建GString的时候p的值是指向Sam对象的指针,GString引用的是p的值,也就是sam变量的指针,所以,等创建完后再改变p是不会变化了,GString引用的是p 的值,不是p变量的引用 ;改变sam的值,GString会同步变化
sam.name = 'Lucy'
assert gs == 'Name: Lucy'
所以如果你想值跟着变化,就需要定义一个无参数的闭包
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'
最后
以上就是传统高跟鞋为你收集整理的Groovy 闭包前言1 闭包语法2 闭包参数3 代理策略4 GString 中的闭包的全部内容,希望文章能够帮你解决Groovy 闭包前言1 闭包语法2 闭包参数3 代理策略4 GString 中的闭包所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复