Gradle 是一个基于 JVM 的新一代构建工具,目前已经应用于多个 Android 开发的技术体系中,比如构建系统、插件化、热修复和组件化等等,如果你不了解 Gradle,那么你对于上述技术体系的了解会大打折扣。
Groovy 是 Apache 旗下的一种基于JVM的面向对象编程语言,既可以用于面向对象编程,也可以用作纯粹的脚本语言。在语言的设计上它吸纳了 Python、Ruby 和 Smalltalk 语言的优秀特性,比如动态类型转换、闭包和元编程支持。Groovy 与 Java 可以很好的互相调用并结合编程 ,比如在写 Groovy 的时候忘记了语法可以直接按 Java 的语法继续写,也可以在 Java 中调用 Groovy 脚本。比起 Java,Groovy 语法更加的灵活和简洁,可以用更少的代码来实现 Java 实现的同样功能。它即可以支持面向对象编程(基于 Java 的扩展),也可以支持面向过程编程(基于众多脚本语言的结合)。需要注意的是,在我们使用 Groovy 进行 Gradle 脚本编写的时候,都是使用的面向过程进行编程的。
Groovy 的语法跟 kotlin 类似,如果我们有 kotlin 的经验的话那么学习 groovy 会容易很多。下面我们一起来学习下 groovy 的核心语法,大家可以跟着样例敲一下加深理解、记忆。
真正意义上讲 groovy 中是不存在基本类型的,无论我们定义基本类型还是对象类型,都会被转换为对象类型。下面我们看个例子:
可以看到,基本类型会被我们的编译器编译成包装类型。
groovy 变量的定义与 Java 中的方式有比较大的差异,对于 groovy 来说,它有两种定义方式,如下所示:
下面,我们就使用 def 关键字来定义一系列的变量,并输出它们的类型,来看看是否编译器会识别出对应的类型,其结果如下图所示:
可以看到,编译器的确会自动自动推断对应的类型。那么,这两种方式应该分别在什么样的场景中使用呢?根据我们的经验:
如果此时我们在后面的代码中改变上图中 x 的值为 String 类型,那么 x 又会被编译器推断为 String 类型,如下图所示:
于是我们可以猜测到,其实使用 def 关键字定义出来的变量就是 Obejct 类型。
Groovy 中的字符串与 Java 中的字符串有比较大的不同,所以这里我们需要着重了解一下。Groovy 中的字符串除了继承了 Java 中传统 String 的使用方式之前,还新增了一个 GString 类型,它的使用方式至少有七、八种,但是常用的有三种定义方式。此外,在 GString 中新增了一系列的操作符,这能够让我们对 String 类型的变量有 更便捷的操作。最后,在 GString 中还新增了一系列好用的 API,我们也需要着重学习一下。
在 Groovy 中有三种常用的字符串定义方式,如下所示:
我们先看看下面:
可以看到,不管是单引号、三引号还是双引号,类型都是 String。
双引号不同与单、三引号,它还可以定义一个可扩展的变量。如下图所示:
可以看到,它不再是 String 类型,还是我们在思维导图中提到的 GString 的实现类。也就是说如果我们使用了可拓展的双引号定义方式,它返回的就是 GString 类型。如果没有使用扩展表达式,则返回的还是 String 类型。它的用途是最广泛的,并且 ${...} 里面不仅仅可以是变量,还可以是任意表达式,可以看下这个例子:
可以看到表达式被正确计算,并且融入到我们的字符串中。
在 Groovy 中 ,String 和 GString 之间是如何区分的,我们看看下面:
由上可见,String 和 GString 是可以相互传递、相互调用的,我们在写代码的时候可以不用刻意区分这两种类型,它们的使用是互相融合的,不需要我们操心。
String 方法的来源有以上以上三种,我们这里主要列举一部分 StringGroovyMethods 中的普通类型的参数的方法:
逻辑控制这一部分对于任何语言来说都是一样的,都是下面这三部分:
我们主要看看与 java 中的不同之处,主要是 switch/case 和 for循环。
通过这段简单的代码就可以看出 groovy 中的 swicth/case 是非常强大的,可以取代 java 中难度的 if 嵌套了。下面我们看看 for 循环的例子:
闭包的本质其实就是一个代码块,没有什么其他特殊的地方,使用起来跟我们的方法更相似,但是又与我们的方法有很大的差别。
我们先看看闭包的定义和调用:
上图是无参闭包的定义和调用,调用方式有两种。下面我们再来看看有参的闭包:
下面我们来看看闭包的返回值:
可以看到我们没有在闭包里 return 一个返回值,但是得到的返回值是 null,大家不要以为返回 null 就是没有返回值,这里返回 null 也是返回值的一个标致,从这里可以看出闭包是一定有返回值的。
下面我们看一个求阶乘的方法:
我们跟进去看看 upto 方法:
在 upto 方法中,每次都会去执行闭包里的代码。可见有了闭包后,我们可以不需要写那么多循环在我们自己的代码里了。我们再来看看实现阶乘的另一种写法:
需要注意的是,groovy 闭包作为方法的最后一个参数是可以写在圆括号外面的,这是 groovy 中最常见的写法。下面我们继续看一个累加的例子:
跟进去看看 times 方法:
上述就是基本类型与闭包的结合用法,如果想用其他的用法,就需要到 DefaultGroovyMethod 中来看一下它里面的哪些方法是与我们的闭包结合使用的。
String 的遍历方法:
find 来查找符合条件的第一个:
跟进去看看 find 方法:
可以看到,它会遍历 self,每次遍历都回调用条件闭包,所以我们的闭包必须要有一个参数,并且返回值是布尔类型。所以 find 方法会找到符合条件的第一个对象返回。
与 find 方法成对的还有一个 findAll 方法:
findAll 方法返回的是一个集合:
所以 findAll 是将符合条件的所有值添加到集合里。
再看看 any 和 every:
再看看 collect 方法:
可以看到上述的方法,很类似我们的 java 中的接口回调。闭包与数据结构和文件的使用我们后面再讲解。
可以看到 this、owner、delegate 分别代表的对象,如果是在类中定义的闭包,那么三者代表的是同一个对象。下面我们可以验证一下:
这里返回的是 Person 类的编码信息,再把 static 去掉:
可以看到返回的是 Person 类的实例。
如果是闭包中再定义一个闭包,那么 this、owner、delegate 返回的就不一样了:
默认情况下 owner 和 delegate 是相同的,那么什么时候两者不相同呢,下面我们再来看看:
看上述代码我们可以知道了,人为修改默认的 delegate 指向对象,那么这种情况下 delegate 就与 owner 不一样了。需要注意的是 this 和 owner 是不可以修改的。
我们先看看下面这段代码:
我们看到,通过修改 delegate 并讲委托策略修改为 DELEGATE_FIRST 后,Student 中的闭包找到了 Teacher 中的 name。假如 Teacher 中没有 name 属性呢,下面我们验证下:
输出结果符合我们的预期,也就是说 DELEGATE_FIRST 是先从委托类中去寻找,如果找不到则去 owner 所指向对象中寻找。而如果将委托策略修改为 DELEGATE_ONLY ,那么在 delegate 中找不到的话就会报错了:
通过在源码中我们可看到,一共有四种委托策略:
list 即链表,其底层对应 Java 中的 List 接口,一般用 ArrayList 作为真正的实现类,List 变量由[]定义,其元素可以是任何对象。链表中的元素可以通过索引存取,而且不用担心索引越界。如果索引超过当前链表长度,List 会自动往该索引添加元素。
我们来看看 groovy 中 list 和 数组的定义的区别:
下面,我们看看 list 最常使用的几个操作。
排序:
查找:
可以看到,groovy 更简洁也有更多的扩展,不用像 java 那样不断的写循环。
定义:
上述的代码可以看出 groovy 的 map 操作跟 java 一样都是很简单的,下面我们来看看 map 常用的操作方法。
遍历:
查询:
排序:
Range 是 List 的子类,当需要 list 的轻量级功能时可以选择用 Range,下面我们看一下 Range 的常用操作。
遍历:
switch case 中使用:
groovy中类、接口等的定义和java基本一样,但是如果不声明 public/private 等访问权限的话,Groovy 中类及其变量默认都是 public 的。另外,groovy中还有一个 trait 关键字:
它的作用类似于接口,相比接口,它的方法可以有默认实现。有点适配器模式的味道。
元编程即Groovy运行时,它的逻辑处理流程图如下:
为了更好的讲解元编程的用法,我们先创建一个 Person 类并调用它的 cry 方法,代码如下所示:
// 第一个 groovy 文件中
def person = new Person(name: 'Qndroid', age: 26)
println person.cry()
// 第二个 groovy 文件中
class Person {
String name
Integer age
def increaseAge(Integer years) {
this.age += years
}
//一个方法找不到时,调用它代替
def invokeMethod(String name, Object args) {
return "the method is ${name}, the params is ${args}"
}
def methodMissing(String name, Object args) {
return "the method ${name} is missing"
}
}
为了实现元编程,我们需要使用 metaClass,具体的使用示例如下所示:
ExpandoMetaClass.enableGlobally()
//为类动态的添加一个属性
Person.metaClass.sex = 'male'
def person = new Person(name: 'Qndroid', age: 26)
println person.sex
person.sex = 'female'
println "the new sex is:" + person.sex
//为类动态的添加方法
Person.metaClass.sexUpperCase = { -> sex.toUpperCase() }
def person2 = new Person(name: 'Qndroid', age: 26)
println person2.sexUpperCase()
//为类动态的添加静态方法
Person.metaClass.static.createPerson = {
String name, int age -> new Person(name: name, age: age)
}
def person3 = Person.createPerson('renzhiqiang', 26)
println person3.name + " and " + person3.age
需要注意的是通过类的 metaClass 来添加元素的这种方式每次使用时都需要重新添加,幸运的是,我们可以在注入前调用全局生效的处理,代码如下所示:
ExpandoMetaClass.enableGlobally()
// 在应用程序初始化的时候我们可以为第三方类添加方法
Person.metaClass.static.createPerson = { String name, int age ->
new Person(name: name, age: age)
}
对于每一个 Groovy 脚本来说,它都会生成一个 static void main 函数,main 函数中会调用一个 run 函数,脚本中的所有代码则包含在 run 函数之中。我们可以通过如下的 groovyc 命令用于将编译得到的 class 文件拷贝到 classes 文件夹下:
// groovyc 是 groovy 的编译命令,-d classes 用于将编译得到的 class 文件拷贝到 classes 文件夹 下
groovyc -d classes test.groovy
当我们在 Groovy 脚本中定义一个变量时,由于它实际上是在 run 函数中创建的,所以脚本中的其它方法或其他脚本是无法访问它的。这个时候,我们需要使用 @Field 将当前变量标记为成员变量,其示例代码如下所示:
import groovy.transform.Field;
@Field author = lerendan