Android Gradle(一)——Groovy基础

一、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匿名内部类的繁琐,使代码变的灵活,轻量,以及可复用。

<font face="微软雅黑" size = 4>闭包实质就是可以用作函数参数和方法参数的代码块,可以将这个代码块理解为一个函数指针</font>

定义闭包

闭包在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

分析上段代码:

  1. 闭包run3传入了参数3,即m = 3,并返回一个闭包{n-> 3*n}
  2. 返回的闭包继续传入参数run2(5,run1),即n = run2(5,run1),返回一个数值为3*run2(5,run1)
  3. 继续看run2(5,run1),run2接受参数5和run1,返回值为run1(5),即为25
  4. 所以结果为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:第一次写博客,请多多指教!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,560评论 4 361
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,104评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,297评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,869评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,275评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,563评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,833评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,543评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,245评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,512评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,011评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,359评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,006评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,062评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,825评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,590评论 2 273
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,501评论 2 268