前言
在上一篇文章中,主要讲解了Gradle基本介绍以及Groovy对应的基础语法,在这一篇中将会从如下几点继续深入Groovy。
- Groovy类与方法
- Groovy闭包
话不多说,直接开始。
1. Groovy类与方法
1.1 多种访问get/set方式
class Car {
def miles
private def year
def getMiles() {
println "getMiles"
return miles
}
private void setMiles(miles) {
println "setMiles"
this.miles = miles
}
}
def car = new Car()
car.miles = 20000
println car.miles+"\n\n"
car.@miles=10000
println car.@miles+"\n\n"
car.year = 2027
println car.year
这里没啥好说的,无非就是对类里面的成员变量执行了赋值与取值操作,但注意在这里我特意将对应setMiles方法以及year变量设置成了私有,接下来我们看运行效果
setMiles
getMiles
20000
10000
2027
我这为了区分看对应的作用,故意用了换行,从这个运行效果可知:
- 所有的变量默认是public的
- 如果要设置为私有禁止直接访问,仅申明private是不行的。依然可以使用’.'直接访问
- 默认会生成getter, setter方法 (参考year)
- 并且可以直接像使用成员变量的方法来自动判断调用getter/setter (参考miles)
- 当进行赋值时调用setter方法
- 当直接访问值时调用的是getter方法
- 使用’.@'才是真正的直接访问变量,跳过默认的getter/setter方法调用 (参考.@miles)
当然这是很标准的get/set访问的方式,除此之外还可以通过’.变量名’进行访问
def car = new Car()
car.@year=2021
car.@miles=30000
// 通过'.变量名'进行访问
println car.'miles'+"\n\n"
println car.@'miles'+"\n\n"
def str = "year"
println car."$str"
运行效果
getMiles
30000
30000
2021
从这可知:通过’.变量名’进行访问依然会调用对应的getter/setter方法,因此通过这样的方式,代码可以更动态、灵活了!
1.2 构造方法
class Car {
def miles
private def year
def getMiles() {
println "getMiles"
return miles
}
private void setMiles(miles) {
println "setMiles"
this.miles = miles
}
}
def car = new Car(miles: 20000, year: 2021)
println(car.@miles+"\n\n")
car=new Car(miles: 30000)
println(car.@miles)
注意看,这里的 Car 里面并没有带参数的构造方法,但实例化Car的时候却强制指定对应的变量以及对应的值,我们看看运行后的效果是怎样的
setMiles
20000
setMiles
30000
从这个运行效果我们可知:
- 构造方法重载规则跟Java一样。
- 在构造方法中传入具名参数,但是要注意:传入的参数都是键值对,实则就是一个Map类型
- 这种方式传入的参数会自动拆解Map并且调用setter方法对应的进行赋值
这里我们看到当没有有参构造方法的时候,会自动拆解Map键值对,那么如果有参构造方法存在会是怎么回事呢?
class Car {
def miles
private def year
Car(){
}
Car(def miles){
this.miles=miles
}
Car(def miles,def year){
this.year=year
this.miles=miles
}
def getMiles() {
println "getMiles"
return miles
}
private void setMiles(miles) {
println "setMiles"
this.miles = miles
}
}
def car = new Car(miles: 20000, year: 2021)
println(car.@miles)
println car.@miles.getClass()
println "\n\n"
car=new Car(miles: 30000)
println(car.@miles)
println car.@miles.getClass()
println "\n\n"
car=new Car(40000)
println(car.@miles)
println car.@miles.getClass()
现在我们看到,我这吧所有构造方法全实现了,现在我们看看运行效果是怎样的。
[miles:20000, year:2021]
class java.util.LinkedHashMap
[miles:30000]
class java.util.LinkedHashMap
40000
class java.lang.Integer
这里我们可以看出:如果参数中还有非键值对的传参,就会把这些键值对当成Map了不会再进行自动拆解赋值。所以要有对应的构造方法才行。
1.3 操作符重载
学过Kotlin的小伙伴应该都清楚,任何对象都可以进行操作符重载,现在来看看Groovy的是怎样的
class Car {
def miles
private def year
Car(){
}
def plus(Car car){
//最后一句的结果能决定方法返回值以及返回类型
this.year+car.year
}
}
def car=new Car()
car.@year=2020
def car2=new Car()
car2.@year=2021
println car+car2
来看看运行效果
Connected to the target VM, address: '127.0.0.1:57371', transport: 'socket'
4041
Process finished with exit code 0
这里我们看到,这里居然出现了对象+对象运算,按常理说这不是只有数字类型的对象才能这样写么?没错!在Groovy里面确实能够这样写,只不过需要实现对应操作符的重载方法。我这只举了一个经典的加法,我这把常用的贴出来,剩下的读者可以依次尝试。
2. Groovy闭包
Groovy中可以理解为闭包就是可执行的代码块,或匿名函数。闭包在使用上与函数与许多共通之处,但是闭包可以作为一个函数的参数。
2.1 普通闭包操作
def closure = {
// 1.默认能够接受一个参数,这个参数可以直接使用it
//-> 2.使用这种方式,闭包不能再传入参数
a = 1, b = 2, c -> // 3.定义多个参数,并且可以使用方法一样去设置默认参数值
println "a=$a b=$b c=$c"
}
closure(30)
println closure.class
运行效果
a=1 b=2 c=30
class com.zee.gradle.test01.GroovyTest02$_run_closure1
这个对于使用Kotlin的小伙伴来说完全可以说一眼就懂,这里用java代码解释就好比这样。
void closure(int c){
int a=1;
int b=2;
System.out.println("a="+a+" b="+b+" c="+c)
}
相信这样大家都应该能秒懂了。
2.2 柯里化闭包
在Groovy中,柯里化的概念只得到了部分应用。它不符合函数式编程中柯里化的真正概念,因为Groovy在闭包上应用了不同的作用域规则。Groovy中的柯里化将允许您设置闭包中一个参数的值,并且它将返回一个接受剩余参数的新闭包。
2.2.1 左柯里化
//闭包定义了两个参数,
def content={int count,String str -> str*count}
//curry 方法会将上面闭包的第一个参数设置为2,调用twice只需要传入第二个参数字符串就可以完成闭包调用
def twice=content.curry(2)
//验证左柯里化是否为闭包表达式里面的 str*count
println twice('a')=='aa'
//验证直接调用闭包是否与左柯里化结果相同
println content(2,'a')==twice('a')
运行效果
true
true
在开头,定义了两个参数的闭包,当执行 content.curry(2) 代码时,此时的 twice 闭包就变成了
def twice={
int count=2,String str->str*count
}
此时调用twice方法就和上面普通闭包一样了。既然有左柯里化,那么肯定会有右柯里化,那么看看。
2.2.2 右柯里化
//闭包定义了两个参数,
def content={int count,String str -> str*count}
//rcurry方法会将上面闭包的第二个参数设置为‘b’,调用twice只需要传入第一个参数字符串就可以完成闭包调用
def twice=content.rcurry('b')
//验证右柯里化是否等于最终结果
println twice(3)=='bbb'
//验证右柯里化是否等于直接调用的结果
println twice(3)==content(3,'b')
这里可以看出,左柯里化以及右柯里化都是针对于两个参数的闭包而形成的,那么如果出现超过2个参数的闭包该怎么使用柯里化呢?
2.2.3 基于索引的柯里化
//定义了三个参数的闭包
def volume={double a,double b,double c->a*b*c}
//设置下标index=1位置(第二个参数的初始值为2d)
def fixedWidthVolume=volume.ncurry(1,2d)
//将设置过第二个参数初始值的闭传入第一个和第三个变量后和直接调用的方式相互比较
println volume(3d,2d,4d)==fixedWidthVolume(3d,4d)
//从下标inde=1位置开始依次赋值初始值,然后得到新的闭包
def fixedWidthAndHeight=volume.ncurry(1,2d,4d)
//将新的闭包与直接调用的方式相互比较
println volume(3d,2d,4d)==fixedWidthAndHeight(3d)
这里注释写的挺全的,看看运行效果
true
true
2.2.4 柯里化闭包总结
- 左右柯里化仅仅针对两个参数的闭包
- 使用左柯里化需要用curry关键方法
- 使用右柯里化需要用rcurry关键方法
- 对于多于两个参数的闭包,可以使用基于索引的柯里化
- 使用基于索引的柯里化需要用到ncurry关键方法
- ncurry方法第一个参数是对应开始的下标,第二个变量可多个,表示从哪开始赋值
2.3 闭包与接口/类进行转换
2.3.1 闭包可以作为方法的参数
def func3(closure2) {
closure2()
}
func3 {
// 闭包执行的代码
println "call"
}
运行效果
call
这里先是定义了方法 func3,然后也写了对应 func3的 闭包代码,在方法里面调用了传入的闭包方法代码,也就是说,方法func3的方法参closure2就为下面func3的闭包代码,于是最终就运行了闭包里面的代码。
2.3.2 闭包与类进行转换
demo1
interface Action {
void call()
void call2()
}
Action closure1 = new Action() {
@Override
void call() {
println "call1"
}
@Override
void call2() {
println("call2")
}
}
def func2(closure3) {
closure3()
}
println closure1.class
func2(closure1)
运行效果
class com.zee.gradle.test01.GroovyTest02$1
call1
即使定义的方法传入的是闭包,但是如果传入的对象的类型也有call方法,那么,是可以执行这个对象的call方法的,实际上,闭包执行的也是call方法。
demo2
class Action {
void call(a) {
println "$a"
}
}
def a = new Action()
a(111)
运行效果
111
这里解释和上面一样:如果传入的对象的类型也有call方法,那么,是可以执行这个对象的call方法的,实际上,闭包执行的也是call方法
2.4 闭包重要的成员变量
//默认指向owner,但可以设置为其他对象
private Object delegate;
//如果闭包在另外一个闭包中创建,owner指向另一个的闭包,否则指向this
private Object owner;
//创建闭包的对象(上下文)
private Object thisObject;
//默认值为0 ,与代理策略相关系
private int resolveStrategy = OWNER_FIRST;
//存放每个参数类型的数组
protected Class[] parameterTypes;
//最多能放多少个参数
protected int maximumNumberOfParameters;
这分别用代码看看
def closure = {
int a, int b ->
a+b
}
closure(1, 2)
println closure.delegate
println closure.owner
println closure.thisObject
println closure.resolveStrategy
println closure.parameterTypes
println closure.maximumNumberOfParameters
运行效果
com.zee.gradle.test01.GroovyTest02@7dac3fd8
com.zee.gradle.test01.GroovyTest02@7dac3fd8
com.zee.gradle.test01.GroovyTest02@7dac3fd8
0
[int, int]
2
没啥好说的,就是一系列变量,但这些和对应闭包策略息息相关,接下来继续看闭包代理策略。
2.5 闭包中代理策略
class Test2 {
def func() {
println "Test2 func"
}
}
def func() {
println "Script func"
}
def closure = {
func()
}
closure()
closure.delegate = new Test2()// 优先选择的是owner
//* @see groovy.lang.Closure#DELEGATE_FIRST // delegate优先
//* @see groovy.lang.Closure#DELEGATE_ONLY // 只在delegate中找
//* @see groovy.lang.Closure#OWNER_FIRST // owner优先
//* @see groovy.lang.Closure#OWNER_ONLY // 只在owner找
//* @see groovy.lang.Closure#TO_SELF // 只在自身找(闭包内部),意义不大
closure.resolveStrategy=Closure.DELEGATE_FIRST
closure()
这里首先有个闭包,闭包里面会调用 func 方法,然后在class内部和外部分别有对应的func方法,在设置策略前后都分别调用了对应的闭包。
现在closure.resolveStrategy选择DELEGATE_FIRST或者DELEGATE_ONLY 来看看效果
Script func
Test2 func
也就是说,当选择DELEGATE_FIRST或者DELEGATE_ONLY时,第一次调用设置过策略的闭包时,将会调用类里面的方法。
那看看选择 OWNER_FIRST 或者 OWNER_ONLY的效果
Script func
Script func
也就是说,当选择OWNER_FIRST 或者 OWNER_ONLY时,将会直接调用外部代码,类里面对应的方法也就失效了。
3. 结束语
好了,到这里,相信你对Groovy语法有了深一步的认识。在下一篇中,将会对Groovy的元编程进行详解。