一、Groovy概述
Groovy是基于JVM的一种动态语言,它结合了Python、Ruby和Smalltalk的特性,同时能与Java代码很好的结合,用于扩展现在的代码,具有以下特点:
- 具有动态语言的特点,如动态类型转换、闭包和元编程
- 面向对象编程,同时也可以作为脚本语言
- 直接编译成Java字节码,在任何使用Java的地方都可以使用Groovy
- Groovy完全兼容Java语言,无缝集成所有Java已有的库和对象
- 支持函数式编程,无需main函数
- 支持DSL,gradle自动化构建系统就是用Groovy语言实现的
二、Groovy语法
Groovy完全兼容Java,所以在Groovy文件中可以写任何的Java代码来进行编程,这里主要是讲Groovy不同于Java的语法特性(在Groovy中所有的代码都不必要用;
结尾):
1.变量
Groovy具有动态类型转换的特点,所以类型对于变量,属性,方法,闭包参数以及函数的返回类型都是可有可无的,在给变量赋值时才确定它的类型,并且在需要时,很多类型之间的转换都可以自动发生。这里要注意的是Groovy可以直接编译成class字节码,JVM是无法判断字节码是Java生成的还是Groovy生成的。除了基本类型外,Groovy中的变量类型都是对象
下面是一些常见变量的声明的例子
字符串
def str1 = '单引号' //Groovy中的字符串可以用单引号来表示
def str2 = "双引号" //也可以用双引号表示
Groovy中的单引号声明的字符串中不具有运算能力,即不能包含运算表达式,而双引号可以通过${str1}
的方式来进行运算
println '单引号的变量运算:${str1}'
println "双引号的变量运算:${str2}" //只有双引号才能运算
println "运算表达式结果为:${str1+str2}" //${}中可以进行运算
输出为:
单引号的变量运算:${str1}
双引号的变量运算:双引号
运算表达式结果为:单引号双引号
集合
Groovy兼容了Java中的集合,并进行了扩展,如Groovy中的集合中的元素允许使用不同的类型。注意,在Groovy中定义的集合是Java中对应类的对象,可以使用任何Java库提供的方法,如size(),add(E e)
等方法。这里主要讲常见的List和Map
List
Groovy中的List无需实现List接口,声明如下:
def numList = [1,2,3,4,5,6]
def mixList = ['1','2',"start",4,5,6] //Groovy可以自动进行动态类型转换
默认的List是一个ArrayList类型,比如我们可以通过如下方式查看它的类型:
println numList.getClass().name //输出为:java.util.ArrayList
Groovy访问List的方式与Java不同,无需通过get方法,而是直接使用下标来进行访问的:
println mixList //输出整个List,为:[1, 2, start, 4, 5, 6]
println numList[0] //访问第一个元素
println numList[1] //访问第二个元素
println numList[-1] //访问倒数第一个元素
println numList[-2] //访问倒数第二个元素
println numList[1..3] //访问第二个到第四个元素,输出为:[2, 3, 4]
println numList[1..3].getClass().name //java.util.ArrayList
Groovy中List还提供了each方法传入闭包来进行迭代操作
numList.each {
println it
}
输出为:
1
2
3
4
5
6
Map
Map的用法类似于List,区别只是Map是通过键值对的方式来声明的:
def map = ['name':'Mike', 'age':20]
println map.getClass().name //java.util.LinkedHashMap
Groovy中的Map默认是LinkedHashMap类型,访问可以通过map[key]
或者map.key
来进行访问:
println map['name'] //输出为:Mike
println map.age //输出为:20
Map也有与List类似的迭代操作:
map.each {
println "Key:${it.key}, Value:${it.value}"
}
输出为:
Key:name, Value:Mike
Key:age, Value:20
2.函数
函数与方法的区别在于函数式独立于类外的,而方法是类中的方法,是一种特殊的函数,两者基本语法相同,方法多了一些类的特性和访问权限而已
Groovy中的函数与Java中的不同点如下:
- 可以使用def来定义,定义形参时可以不用显示定义类型,Groovy会自动进行动态类型转换
- 参数外的括号可以省略,函数名和参数列表之间用空格隔开,无参函数必须带括号
- 函数嵌套在同一行调用时,只有第一个函数可以省略括号
- 无需return语句,Groovy会自动将函数中真正执行的最后一句代码作为其返回值,如果使用void修饰函数,函数会返回null(比如定义变量或者直接给出常量,就是有返回值,调用无返回值的函数,如println,就是无返回值)
例1:
fun1(10, 11) //输出为:21
fun1 8,9 //输出为:17
def fun1(int a, int b) {
println a+b
}
例2:
println fun1(10, 11) //输出为:21
//println fun1 8,9 这是错误的写法,会报错
println fun1('abc','def') //输出为:abcdef
def fun1(a, b) {
a
b
a+b
}
3.类
在Groovy类中默认修饰符是public,没有default修饰符。
Groovy会自动给类中的变量设置setter和getter方法(你当然可以自己手动重写它们),我们可以直接通过类名.成员
来调用这些方法来对类中的成员变量进行访问和修改。(请注意Groovy自动设置的setter和getter不能通过方法名去调用它们,如果你确实要这样做,请在类中自定义这些方法)
Person p = new Person()
println "名字为:${p.name}" //默认为null,输出为:名字为:null
println p.i //默认为0,输出为:0
println p.b //默认为false,输出为:false
p.name = 'Mike'
println "名字为:${p.name}" //输出为:名字为:Mike
class Person {
private String name
private boolean b
private int i
}
你也可以手动重写getter方法:
Person p = new Person()
println "名字为:${p.name}" //输出为:名字为:abc
p.name = 'Mike'
println "名字为:${p.name}" //输出为:名字为:abc
class Person {
private String name
public String getName() {
return "abc"
}
}
在类中可以直接通过成员名访问从父类继承来的私有成员,但是这种情况父类必须自己定义getter/setter方法
class C1 {
private int x = 5
//必须包含这个方法,不然子类无法直接使用x
int getX() {
return x
}
}
class C2 extends C1 {
public test() {
println x
}
}
new C2().test() //5
.
运算符并不一定是操作类的成员变量的,其实质只是调用了setter/getter方法而已:
User user = new User()
println user.age //输出为:12
//user.age = 10 User中没有定义setAge方法,所以会报错
class User {
public int getAge() {
12
}
}
在Gradle中有很多类似于这样的用法,其实类中并没有定义对应的属性,只不过是定义了相应的getter/setter方法而已
三、Groovy闭包
闭包是Groovy非常重要的一个特性,也是DSL的基础。闭包使Groovy省去了Java匿名内部类的繁琐,使代码变的灵活,轻量,以及可复用。
闭包实质就是可以用作函数参数和方法参数的代码块,可以将这个代码块理解为一个函数指针
定义闭包
闭包在Groovy中是groov.lang.Closure类,可以用Closure来声明,也可以用def来声明
Groovy中闭包的定义如下:
def xxx = {[params -> ] code} //记住 -> 是连在一起的,不能分开
params可以理解为是函数的形参,形参名不能和外部变量同名,如果只有一个参数,可以将不指定参数,Groovy会指定一个隐式的默认参数it,如果没有参数,也可以不指定参数:
Closure closure1 = {
println it
}
Closure closure2 = {
k,v ->
println "${k} is ${v}"
}
调用闭包
闭包可以直接当做一段代码来调用,通过闭包.call(参数)
或者闭包(参数)
来调用,也可以作为参数传递到函数中在调用。闭包跟方法一样,可以省略括号,用空格代替,当然无参闭包必须带上括号,否则会输出闭包的类对象
closure1.call('Mike') //Mike
closure2('name', 'Mike') //name is Mike
closure2 'name', 'Mike' //name is Mike
closure3.call() //There is no parameter
需要注意的是,闭包是可以访问外部变量的,而函数不行
String name = 'Jack'
Closure closure4 = {
println name
}
closure4() //Jack
闭包跟函数一样,是有返回值的,默认最后一行语句是闭包的返回值,如果最后一行语句没有返回类型,那么返回null(比如定义变量或者直接给出常量,就是有返回值)
//定义一个有返回值的闭包
Closure closure5 = {
'hello world'
}
//打印两个闭包的返回值,closure4最后一句是没有返回值的
println closure4() //null
println closure5() //hello world
闭包可以视为一个普通的变量,所以闭包可以作为参数传递给函数,或者另一个闭包,也可以作为闭包的返回值返回
def run1 = {a -> a * a}
def run2 = {x, y -> y(x)}
def run3 = {m -> {n -> m * n}}
println run3(3)(run2(5,run1)) //输出为:75
分析上段代码:
- 闭包run3传入了参数3,即m = 3,并返回一个闭包
{n-> 3*n}
- 返回的闭包继续传入参数run2(5,run1),即n = run2(5,run1),返回一个数值为3*run2(5,run1)
- 继续看run2(5,run1),run2接受参数5和run1,返回值为run1(5),即为25
- 所以结果为75
对于闭包有一些省略的写法:
- 当匿名闭包作为参数时,如果它是最后一个参数,或者是唯一的参数,可以将闭包从括号中拉出来,放在括号后面:
//之前的代码可以写成如下形式:
println run3(3)(run2(5){a -> a * a}) //将run1从run2参数列表中拉出
再看下List的each方法的写法:
numList.each {println it}
实质是List.each(closure),即真正写法是这样的:
numList.each({println it})
然后将闭包从参数列表中拉出:
numList.each(){println it}
在根据函数和闭包的参数写法可以省略括号,用空格代替:
numList.each {println it}
闭包的参数可以接受Map和List:
- 闭包参数中与键值关系有关的参数,会自动封装起来放到闭包的第一个参数
def x = {a, b, c -> a.x * a.y + b + c}
println x(5, 7, x:2, y:3) //18
- 如果闭包参数列表中本身没有List,那么传入List会将List中的元素依次匹配到参数列表
def c = {a, b ,c -> a * b + c}
def list = [1,2,3]
println c(list) //5
闭包委托
委托策略是Groovy闭包中独有的语法,Groovy通过闭包委托策略使得DSL语言更加优化和简洁,在Gradle中就大量使用了闭包的委托策略。
在抽象类groovy.lang.Closure中包含了三个成员变量:
private Object delegate;
private Object owner;
private Object thisObject;
三个成员含义如下:
- thisObject指闭包所在的类,注意是类对象,并且是在闭包定义时的外围类对象,匿名闭包的thisObject并不是其调用者
- owner指闭包上层的对象,即包含闭包的类或者闭包(多层嵌套的情况下)
- delegate默认为owner
根据前面讲的Groovy中类中成员的访问方式,Closure是抽象类,其中定义了对应的getter,delegate还设置了对应的setter方法。我们定义的都是它的实现子类,可以在闭包中通过三种成员名来直接访问这三种成员。
class C1 {
def firstClosure = {
println "firstClosure.thisObject:${thisObject.getClass()}"
println "firstClosure.owner:${owner.getClass()}"
println "firstClosure.delegate:${delegate.getClass()}"
def secondClosure = {
println "secondClosure.thisObject:${thisObject.getClass()}"
println "secondClosure.owner:${owner.getClass()}"
println "secondClosure.delegate:${delegate.getClass()}"
}
secondClosure()
new C2().test {
println "test.thisObject:${thisObject.getClass()}"
println "test.owner:${owner.getClass()}"
println "test.delegate:${delegate.getClass()}"
}
}
}
class C2 {
def test(Closure closure) {
closure()
}
}
new C1().firstClosure()
输出如下:
firstClosure.thisObject:class C1
firstClosure.owner:class C1
firstClosure.delegate:class C1
secondClosure.thisObject:class C1
secondClosure.owner:class C1$_closure1
secondClosure.delegate:class C1$_closure1
test.thisObject:class C1
test.owner:class C1$_closure1
test.delegate:class C1$_closure1
Delegate策略
当闭包中出现一个属性没有指定其所有者时,就会执行对应的Delegate策略:
- OWNER_FIRST:这是默认的策略,会优先从owner中寻找对应的属性,如果找不到会去delegate中找
- DELEGATE_FIRST:与OWNER_FIRST相反。
- OWNER_ONLY:只在owner中寻找
- DELEGATE_ONLY:只在delegate中寻找
- TO_SELF:只在闭包自身中找
看如下的例子:
class User {
String name
def c1 = {
//def name = 100
println name
}
}
class Person {
String name
def test(Closure c) {
c()
}
}
String name = 'Wrapper'
User u = new User()
Person p = new Person()
u.name = 'user'
p.name = 'person'
p.test(u.c1) //user
u.c1.setResolveStrategy(Closure.DELEGATE_FIRST)
u.c1.delegate = p
p.test(u.c1) //person
p.test {
println name
} //Wrapper
上述代码,如果在闭包中设置了name = 100
,那么闭包调用时不会调用委托策略,而是直接输出100
第一次输出user是因为默认从owner中找name属性
第二次输出person是因为设置了优先从delegate中寻找name属性
第三次输出Wrapper是因为匿名闭包的owner是外部的类,而非其调用者,所以输出Wrapper
在Gradle中基本上都是用Delegate策略使用闭包来对项目进行配置属性的
ps:第一次写博客,请多多指教!