Groovy基本语法与使用

Groovy简介

Groovy是增强Java平台的唯一的脚本语言;与Java相比,脚本语言具有更好的表示能力,能够提供更高的抽象等级。
Groovy语言特点:

  1. Groovy是一种面向对象的脚本语言,涉及的所有事物都是对象,这一点不像Java语言
  2. Groovy与Java是二进制兼容的,Groovy编译器产生的字节码与Java编译器产生的字节码是完全一样的
  3. Groovy能够完全使用各种Java API
  4. Groovy是动态类型语言,类型标记存在于对象中,而不是由引用它的变量来决定,所以Groovy不要求声明变量的类型、方法的参数或者方法的返回值
  5. Groovy语言内置数据结构,如:List对象或者Map对象

学习这门语言,主要是为了看懂这门脚本语言,知道在Gradle中为什么这么写。我们以后在Gradle中使用Groovy脚本文件,既可以写任何符合Groovy语法的代码,而Groovy又完全兼容java,意味着可以在build脚本文件里写任何的java代码,是相当的灵活。

Groovy基础语法

在Groovy中每一个事物最终会被当作某些类的一个实例对象。例如:Groovy中,整数123实际上是Integer类的一个实例。Groovy同样支持运算符重载,也就是 +号可以出现在 两个参数之间,例如:123+456 , 但是可以通过对象调用的方式,123.plus(456)

Groovy支持整数和浮点数。注意:整数都是Integer类的实例,浮点数是BigDecimal类的实例。

Groovy中使用关键字def 来声明变量。

def b = 3.14
println b.getClass()

// 打印结果
class java.math.BigDecimal

关系运算符(< 、> 、 >= 、<=)和等于运算符(== 、 !=)最终都是调用某个方法实现的。关系运算符通过调用compareTo方法实现的,等于运算符通过调用equals方法实现的。

// 3 > 5
3.compareTo(5) > 0

def age = 23
def number = 24
// age.equals(number)是通过Integer类的equals方法实现
age == number 

字符串

在Groovy中,语句后面分号不是必需的,这是Groovy的特性。

在Groovy中,可以使用单引号**(’)、双引号(")或者三引号(’’’)**来封装字符串。单引号表示纯粹的字符串常量,而不能对字符串里的表达式做运算,但是双引号可以。不管哪种方式表示字符串,它们都是String类型。

def age = 23;
println 'my age is ${age}'
println "my age is ${age}"
println '''my age is ${age}'''
println "my age is \${age}"

// 打印结果
my age is ${age}
my age is 23
my age is ${age}
my age is ${age}

从上面的示例可以看出:单引号中${age}没有被解释;如果在美元符号($)前使用了反斜杠()转义字符后,也不会被解释。也就是说,只在字符串需要被解释的时候使用双引号,在其他情况下字符串使用单引号,单引号没有运算能力

我们可以通过索引(index)来指定字符串中单个字符或者指定字符集合。一般索引从零开始,该字符串的长度值减一结束。如果负数索引,从字符串的末尾开始往前计算。

def string = "Hello Groovy"
println string[1]  // e
println string[-1]  // y
println string[1..2]  // el    [1..2]表示1到2的范围
println string[1..<3]  // el
println string[4, 2, 1] // ole
基本操作
def string = "Hello Groovy"
println "Hello" + " Groovy" // Hello Groovy 字符串的合并,相当于"Hello".plus(" Groovy")
println "Hello" * 3   // HelloHelloHello 字符串的赋值
println string - "o Groovy" // Hell 删除首次出现的字符串
println string.length()   // 12 获取字符串的长度
println string.size()     // 12 获取字符串的长度
println string.contains("Groovy")  // true 字符串中是否包含指定的子字符串
println string.count("o")   // 3 统计某个子字符串出现的次数
方法

Groovy开发工具(GDK)扩展了String类的一些方法,如:

方法名函数原型
centerString center(Number numberOfChars, CharSequence padding);
返回左右均使用padding填充的新字符串,默认使用空格填充
eachMatchString eachMatch( String regex,Closure closure);
判断该字符串是否与正则表达式regex匹配。传递给闭包的对象是一个匹配成功的字符串数组
leftShiftStringBuffer leftShift(Object value);
可以将多个字符串对象相加,并返回新的字符串。与StringBuffer功能一样
nextString next();
字符串的末尾字符替换为末尾字符ASSIC码+1后的字符,并将字符串返回
getAtString getAt(int index);
String getAt(IntRange range);
String getAt(Collection indices);
根据传入的索引或者范围获取对应的字符
padLeftString padLeft(Number numberOfChars, CharSequence padding);
在字符串的左边使用padding填充,默认空格填充
padRightString padRight(Number numberOfChars, CharSequence padding);
在字符串的右边使用padding填充,默认空格填充
plusString plus(CharSequence value);
字符串相加
previousString previous();
字符串的末尾字符替换为末尾字符ASSIC码-1后的字符,并将字符串返回
sizeint size()
返回字符串的长度
splitString[] split(String regex);
与给定正则表达式匹配的规则切割字符串
tokenizeList tokenize(CharSequence delimiters);
使用delimiters作为字符串的分隔符,默认使用空格作为分隔符
reverseString reverse();
创建当前字符串的逆序字符串

下面示例演示:

def string = "Groovy"
println string.size()  // 6

println string.center(4) // Groovy
println string.center(11, '#')  // ##Groovy###

println string.eachMatch("oo") {
    ch -> print ch  // oo
} // Groovy

println string.getAt(0) // G
println string.getAt(1..3) // roo
println string.getAt([0, 2, 4]) // Gov

println string.leftShift("##") // Groovy##

println string.next() // Groovz
println string.previous() // Groovx

println string.padLeft(8, "#") // ##Groovy
println string.padRight(9, "#") // Groovy###

println string.plus(".") // Groovy.

println string.reverse() // yvoorG

println string.split('oo') // [Gr, vy]

println string.tokenize('r') // [G, oovy]
字符串比较

Groovy中,字符串比较采用词典顺序,大写字母排在小写字母前面。如果 str1 与 str2 比较, str1 和 str2 相同,返回0;如果str1 在 str2 前面,返回-1;如果str1在str2后面,返回1

列表(List)、映射(Map)、范围(Range)

这三种数据结构都是引用其他对象的集合。List和Map能够引用不同类型的对象,Range表示整数值的集合。List中每个对象通过一个整数值索引index获取,Map能够通过任意类型的值来索引。Groovy完全兼容了java的集合,并且进行了扩展。

列表(List)

Groovy列表定义方式,下面例子可以看出:Groovy列表类型使用的就是Java中的ArrayList

// 可以在Groovy中直接创建ArrayList方式,但是太繁琐
def list = new ArrayList()
println list.getClass() // class java.util.ArrayList

// Groovy定义方式,是不是很简单啊
def list2 = [11, 12, 13, 14]
println list2.class // class java.util.ArrayList

// 可以嵌套不同类型元素
def list3 = def list3 = [12, 23, "kerwin", [32, 43]]

Groovy列表通过索引操作符[index]来获取列表中的元素值,列表的索引index从0开始,指向列表的第一个元素值。索引操作符其实就是List类中定义的getAt方法。负索引表示从右边开始,-1就是代表从右侧数第一个。

def list = [11, 12, 13, 14]
println list[0]  // 访问第0个元素的值为11,  list.getAt(0) 
println list[3]  // 访问第3个元素的值为14,  list.getAt(3) 
println list[-1]  // 访问最后一个元素14,  list.getAt(-1)

通过索引index范围操作列表,包含边界的索引范围如:start..end,不包含end边界的索引范围如start..<end

def list = [11, 12, 13, 14]
println list[0..2] // [11, 12, 13]
println list[1..<3] // [12, 13]

同样列表的索引操作符也可以为List赋值,也可以通过putAt方法实现赋值操作.

def list = [11, 12, 13, 14]
list[1] = 22
println list.toListString() // [11, 22, 13, 14]
列表(List)操作

在Groovy中,可以通过List类提供的方法操作。但是Groovy提供了非常简单的方法。如集合的迭代操作each方法,该方法接受一个闭包作为参数,可以访问List中的每个元素

def list = [11, 12, 13, 14]
list.each {
    println "${it}"
}
向列表中添加元素

可以通过<<运算符(即leftShift)将一个新元素值追加到列表的最后;
同样可以使用+操作符(即plus)追加元素或者连接两个列表,注意:plus(Collection<T> right)方法返回的List是一个新的列表,元素由原始列表中的元素和right中元素组成.

def list = [11, 12, 13, 14]
list.add(15) // [11, 12, 13, 14, 15]
list.leftShift(16) // [11, 12, 13, 14, 15, 16]
list << 17 // [11, 12, 13, 14, 15, 16, 17]
println list.toListString()

// 注意:plus方法返回是一个新的List列表
def plusList = list.plus([18, 19]) // [11, 12, 13, 14, 15, 16, 17, 18, 19]
println plusList.toListString()

def plusList2 = plusList + [20, 21] // [11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21]
println plusList2.toListString()
从列表中删除元素

调用remove方法删除元素的时候,我们需要注意remove(int index)remove(Object o)的区别,第一个是根据索引index删除列表中的元素,而第二个是根据列表中的元素删除,所以我们可以调用removeAt(int index)removeElement(Object o)来区别

def list = [11, 12, 13, 14]
list.remove(2); // [11, 12, 14]
// 需要注意点
//list.remove(14) // Caught: java.lang.IndexOutOfBoundsException: Index: 14, Size: 3
list.remove((Object)14) // [11, 12]

list.removeAt(1); // [11]
list.removeElement(11) // []

我们可以调用removeAll(Closure condition)同时删除列表中多个元素,参数是闭包,闭包是boolean类型返回值

def list2 = [21, 22, 23, 24]
list2.removeAll {
    return it % 2 == 0
} //

还可以使用-操作符(即minus方法)从列表中删除元素;注意:minus(Collection<?> removeMe)方法返回的List是一个新列表,新列表中的元素由原始列表中的元素移除removeMe中元素组成。

def list3 = [31, 32, 33, 34]
def minusList = list3.minus(32) // [31, 33, 34]
def minusList2 = list3.minus([32, 33]) // [31, 34]
def minusList3 = list3 - [33, 34] // [31, 32]
列表元素排序

在Groovy中排序sort(boolean mutate,Closure closure)方法有多个重载方法,传入的闭包方式需要有比较的参数并返回0、-1、或者1

def list = [12, -3, 2, 8, -1, 34]

// 第一种方式
Comparator mc = {
    a, b -> a == b ? 0 : Math.abs(a) < Math.abs(b) ? 1 : -1
}
Collections.sort(list, mc) // [34, 12, 8, -3, 2, -1]

// 第二种方式
list.sort {
    a, b -> a == b ? 0 : Math.abs(a) < Math.abs(b) ? -1 : 1
} // [-1, 2, -3, 8, 12, 34]

// boolean mutate 该参数表示是否创建新的列表存放排序后的元素
// false: 创建新的列表存放
def sortList = list.sort(false) {
    a, b -> a == b ? 0 : Math.abs(a) < Math.abs(b) ? 1 : -1
} // [34, 12, 8, -3, 2, -1]
列表中查找元素
def list = [-3, 9, 6, 2, -7, 1, 5]
// 返回查找第一个满足条件的元素,没有找到返回null
def result = list.find {
    return it % 2 == 0
} // 6

// 返回查找所有满足条件的元素,没有找到返回空的列表
def resultAll = list.findAll {
    return it % 2 == 0
} // [6, 2]

//列表中只要一个元素满足条件就返回true,否则返回false
def resultAny = list.any {
    return it % 2 == 0
} // true

//列表中所有的元素都满足条件返回true,否则返回false
def resultEvery = list.every {
    return it % 2 == 0
} // false

// 查找列表中最大的值
println list.max {
    return Math.abs(it)
} // 9

// 查找列表最小的值
println list.min() // -7

// 统计列表中满足条件的元素总个数
println list.count {
    return it % 2 == 0
} // 2
映射(Map)

Map用法与List很像。Groovy映射定义方式,从下面例子可以看出:Groovy列表使用的就是Java中的LinkedHashMap

def colors = [red  : 'ff0000',
              green: '00ff00',
              blue : '0000ff']
println colors.getClass() // java.util.LinkedHashMap
println colors.toMapString() // [red:ff0000, green:00ff00, blue:0000ff]
//注意: 

// 空Map定义方式
def nullMap = [:]
println nullMap.toMapString() // [:]

映射赋值及访问可以通过索引的方式,和列表一样,也可以通过getAt方法访问,通过putAt方法赋值。从下面示例可知,Map访问通过map.key或者**map[key]**都可以。

def colors = [red  : 'ff0000',
              green: '00ff00',
              blue : '0000ff']
// 访问
println colors['red'] // ff0000
println colors.blue // 0000ff
println colors['yellow'] // null
println colors.getAt('green') // 00ff00

// 赋值
colors.yellow = 'ffff00'
// 或者  colors['yellow'] = 'ffff00'
// 或者  colors.putAt('yellow', 'ffff00')
println colors.toMapString() // [red:ff0000, green:00ff00, blue:0000ff, yellow:ffff00]
映射(Map)操作

为了方便操作Map,先定义students 集合数据。

def students = [
        1: [number: '0001', name: 'Bob',
            score : 55, sex: 'male'],
        2: [number: '0002', name: 'Johnny',
            score : 62, sex: 'female'],
        3: [number: '0003', name: 'Claire',
            score : 73, sex: 'female'],
        4: [number: '0004', name: 'Amy',
            score : 66, sex: 'male']
]

Map遍历, GDK提供了很多each方法。可以通过Map.Entry、索引index或者key-value方式

// 如果闭包传入一个参数,是通过Map.Entry遍历
students.each {
    def student -> println "the kay is ${student.key}, the value is ${student.value}"
}

// 如果闭包传入2个参数,是通过key-value方式遍历
students.each {
     key, value -> println "the kay is ${key}, the value is ${value}"
}

// 遍历的时候,获取index
students.eachWithIndex{
    def student, int index -> println "index is ${index}, the kay is ${student.key}, the value is ${student.value}"
}
// 遍历的时候,获取index
students.eachWithIndex{
    key, value, index -> println "index is ${index}, the kay is ${key}, the value is ${value}"
}
Map查找
// 在Map中查找第一个满足条件,找到返回的是Entry对象,没有找到返回null
def result = students.find {
    def student -> return student.value.score >= 60
}
println result // 2={number=0002, name=Johnny, score=62, sex=female}

// 在Map中查找所有满足条件的,并将满足条件的数据存放在新的Map中返回
def resultAll = students.findAll {
    key, value -> value.score >= 60
}
//[2:[number:0002, name:Johnny, score:62, sex:female],
// 3:[number:0003, name:Claire, score:73, sex:female],
// 4:[number:0004, name:Amy, score:66, sex:male]]
println resultAll.toMapString()

//查找所有及格的学生姓名
def resultList = students.findAll {
    def student -> student.value.score >= 60
}.collect {
    key, value -> value.name
}
println resultList.toListString() // [Johnny, Claire, Amy]
Map分组
// 按成绩及格与不及格分组
def resultGroupMap = students.groupBy {
    def student -> return student.value.score >= 60 ? "及格" : "不及格"
}
// [不及格:[1:[number:0001, name:Bob, score:55, sex:male]],
//  及格:[2:[number:0002, name:Johnny, score:62, sex:female],
//          3:[number:0003, name:Claire, score:73, sex:female],
//          4:[number:0004, name:Amy, score:66, sex:male]]]
println resultGroupMap.toMapString()
Map排序
// 排序
def resultSortMap = students.sort {
    def student1, def student2 ->
        Number score1 = student1.value.score as Number
        Number score2 = student2.value.score as Number
        return score1 == score2 ? 0 : score1 < score2 ? -1 : 1
}
//[1:[number:0001, name:Bob, score:55, sex:male],
// 2:[number:0002, name:Johnny, score:62, sex:female],
// 4:[number:0004, name:Amy, score:66, sex:male],
// 3:[number:0003, name:Claire, score:73, sex:female]]
println resultSortMap.toMapString()
范围(Range)

范围包含边界的范围通过..表示, 不包含边界的范围则在下边界和上边界之间使用..<。 范围可以通过字符串或者整型值表示.

def range = 1..10
// 获取当前范围中元素个数
println range.size() // 10
// 通过索引获取范围中元素
println range[2] // 3

// 可以通过from、to获取当前范围的最小元素值、最大元素值
println range.from // 1
println range.to // 10

// 判断范围中是否包含给定的元素
println range.contains(4)

//通过字符串表示范围
def start = 1
def finish = 4
def rangeString = start..finish
println rangeString.getClass() // class groovy.lang.IntRange
println rangeString.toListString() // [1, 2, 3, 4]

闭包

闭包是一种表示可以执行代码块的方法,且闭包也是对象,可以像方法一样传递参数。在声明闭包后,可以使用并修改其作用域内的所有变量值。

闭包语法定义

参数列表 -> 实现体

不带参数的闭包,可以省略参数和**->**,调用直接调用call方法

// 闭包及其调用方法
def closure = { println "Hello Groovy" }
closure.call() // Hello Groovy

闭包可以像方法一样,在闭包中声明中引入形参;

// 参数化的闭包
def closure = { name -> println "Hello ${name}" }
closure.call("Groovy") // Hello Groovy
// 省略了call
closure("Gradle") // Hello Gradle

在闭包中,如果只有一个形参时,可以省略形参名,直接只用隐参数it;当有多个参数时,it就不能表示了。

// 单个隐参数
def closure={println "hello ${it}"}
closure.call("Froovy") //hello Groovy
closure("Gradle")
//自定义遍历Map的函数,参数接受一个闭包
def eachMap(closure){
    def map=['name':'kerwin','age':12]
    map.each{
        closure.call(it.key,it.value)
    }
}
// 多个参数
eachMap{
    k,v -> println "${k},${v}"
}

闭包可以访问属性值

// 闭包和作用范围
def string = "hello"
def closure = {
    name -> println "${string} ${name}"
}
closure.call("groocy")
// 修改属性值
string = "Welcome"
closure.call("Groovy") // Welcome Groovy

闭包的作用域

def string = "Hello"
def closure = { name -> println "${string} ${name}" }
closure.call("Groovy") // Hello Groovy

string = "Welcome"
closure.call("Groovy") // Welcome Groovy

def test(clos) {
    def string = "My" // 不生效的代码
    clos.call("book")
}

test(closure) // Welcome book

/**
 * 闭包在实参列表外部
 */
// 调用闭包对象的引用
test closure // Welcome book
// 调用闭字面值
test {
    name -> println "Welcome ${name}"
} // Welcome book

upto方法在每次调用闭包时,其迭代都从它接受的数字值开始,止于给定的参数值。downto方法在每次调用闭包时,其迭代都从他给定的参数值开始,止于他接受的数字值。下面使用闭包求累加示例

println fac(10) // 55

/**
 * 求number的累加, upto
 */
def fac(int number) {
    def result = 0
    1.upto(number) {
        num -> result += num
    }
    result
}


println fac2(10) // 55
/**
 * 求number的累加, downto
 */
def fac2(int number) {
    def result = 0
    number.downto(1) {
        num -> result += num
    }
    result
}

闭包、集合和字符串

闭包常常用于列表、映射和字符串结合使用。例如遍历每一个元素

[11, 12, 13, 14].each {
    print "${it} "
} // 11 12 13 14

["Java": 121, "Android": 122, "Groovy": 123].each {
    println it
}
//Java=121
//Android=122
//Groovy=123

["Java": 121, "Android": 122, "Groovy": 123].each {
    println "key is ${it.key}, value is ${it.value}"
}
//key is Java, value is 121
//key is Android, value is 122
//key is Groovy, value is 123

闭包的特性

由于闭包也是一个对象,所以可以作为方法的参数。

def list = [11, 12, 13, 14]

// 偶数
def isEven = { num -> return num % 2 == 0 }
// 奇数
def isOdd = { num -> return !isEven.call(num) }

// 根据传入的闭包过滤满足条件的元素
def filter(list, clos) {
    return list.findAll(clos)
}

def evens = filter(list, isEven)
println "evens is ${evens}" // evens is [12, 14]

def odds = filter(list, isOdd)
println "odds is ${odds}" // odds is [11, 13]

闭包也可以作为另一个闭包的参数

def table = [11, 12, 13, 14, 15]

// 奇数
def isOdd = { num -> return num % 2 != 0 }
// 偶数
def isEven = { num -> return !isOdd.call(num) }

def filter = { clos, list ->
    def result = []
    for (element in list) {
        if (clos.call(element)) {
            // << 运算符追加新元素
            result << element
        } else {
            result
        }
    }
    return result
}

def odds = filter.call(isOdd, table)
println "odds id ${odds}" // odds id [11, 13, 15]

def evens = filter.call(isEven, table)
println "evens is ${evens}" // evens is [12, 14]

闭包作为返回值

// 返回闭包
def multiply(x) {
    return { y -> x * y }
}

def result = multiply(2)
println "${result.call(3)}" // 6

闭包关键变量

Groovy闭包的强大之处在于它支持闭包方法的委托。Groovy的闭包有this、owner、delegate这三个属性,当在闭包内调用方法时,由它们来确定使用哪个对象来处理。

默认情况下delegateowner是相等的,但是delegate是可以修改的,Gradle中的闭包很多功能都是通过修改delegate实现的。

下面讨论this、owner、delegate闭包三个重要变量:

this代表闭包定义处的类
owner代表闭包定义处的类或者对象
delegate代表任意对象,默认与owner一致

首先test.groovy脚本文件中定一个闭包,然后打印这三个关键字,发现打印的信息一样。

def scriptClouser = {
    println "scriptClouser this: " + this
    println "scriptClouser owner: " + owner
    println "scriptClouser delegate: " + delegate
}
scriptClouser.call()

//scriptClouser this: com.kerwin.groovy.object.test@740cae06
//scriptClouser owner: com.kerwin.groovy.object.test@740cae06
//scriptClouser delegate: com.kerwin.groovy.object.test@740cae06

然后在test.groovy脚本文件中定义一个内部类Person,然后打印这三个关键字,static代表当前类,打印的信息也都一样。

class Person{
    def static classClouser = {
        println "classClouser this: " + this
        println "classClouser owner: " + owner
        println "classClouser delegate: " + delegate
    }
//    classClouser this: class com.kerwin.groovy.object.Person
//    classClouser owner: class com.kerwin.groovy.object.Person
//    classClouser delegate: class com.kerwin.groovy.object.Person

    def static speech() {
        def methodClassClouser = {
            println "methodClassClouser this: " + this
            println "methodClassClouser owner: " + owner
            println "methodClassClouser delegate: " + delegate
        }
//        methodClassClouser this: class com.kerwin.groovy.object.Person
//        methodClassClouser owner: class com.kerwin.groovy.object.Person
//        methodClassClouser delegate: class com.kerwin.groovy.object.Person
        methodClassClouser.call()
    }
}

Person.classClouser.call()
Person.speech()

在test.groovy脚本文件中定义一个闭包,然后在闭包中定一个闭包,然后打印这三个关键字,发现打印信息不同。其实this就是这个构建脚本的上下文

def outerClouser = {
    def innerClouser = {
        println "innerClouser this: " + this
        println "innerClouser owner: " + owner
        println "innerClouser delegate: " + delegate
    }
    innerClouser.call()
}
//innerClouser this: com.kerwin.groovy.object.test@2a265ea9
//innerClouser owner: com.kerwin.groovy.object.test$_run_closure2@793f29ff
//innerClouser delegate: com.kerwin.groovy.object.test$_run_closure2@793f29ff
outerClouser.call()

// 如果修改innerClouser 闭包的delegate,打印信息
innerClouser.delegate = new Person()
//innerClouser this: com.kerwin.groovy.object.test@2a265ea9
//innerClouser owner: com.kerwin.groovy.object.test$_run_closure2@793f29ff
//innerClouser delegate: com.kerwin.groovy.object.Person@3e8c3cb

闭包委托策略

定义一个Student类

class Student {
    def name
    def printName = { "My name is ${name}" }

    @Override
    String toString() {
        return printName.call()
    }
}

定义Teacher类

class Teacher {
    def name
}

设置student的printName闭包delegate为teacher, 闭包策略为DELEGATE_FIRST
DELEGATE_FIRST:首先代理类找,找不到从自己类找,自己类中找不到,抛出groovy.lang.MissingXXXException异常
DELEGATE_ONLY:只会在代理类找, 找不到抛出groovy.lang.MissingXXXException异常

def student = new Student(name: 'kerwin')
def teacher = new Teacher(name: 'CV')
student.printName.delegate = teacher
// 委托模式优先
student.printName.resolveStrategy = Closure.DELEGATE_FIRST
println student.toString() // My name is CV

在Gradle中,一般会指定delegate为当前的it,在闭包内就可以对该it进行配置,或者调用其方法

 person {
     println "this: ${this}"  // this: com.kerwin.groovy.file.test@5a45133e
     println "owner: ${owner}" // owner: com.kerwin.groovy.file.test@5a45133e
     println "delegate: ${delegate}" // delegate: com.kerwin.groovy.Person@771a660
 
     name = 'kerwin'
     age = 23
     printString() // printString: name is kerwin, age is 23
 }


def person(Closure<Person> closure) {
    def p = new Person()
    println "${p}" // com.kerwin.groovy.Person@771a660
    closure.delegate = p
    // 委托模式优先
    closure.resolveStrategy = Closure.DELEGATE_FIRST
    closure.call(p)
}

class Person implements Serializable {
    def name
    def age

    def printString() {
        println "printString: name is ${name}, age is ${age}"
    }
}

上例中,设置闭包的委托对象为当前创建的Person对象,并设置委托模式优先。在使用person方法创建一个Person的示例时,可以在闭包里直接对该Person实例配置。在Gradle中有很多类似的用法,基本上都是使用delegate的方式使用闭包进行配置。

流程控制语句

while语句、for语句、if语句、switch语句。使用方式跟Java一样,下面主要讲解与Groovy不同之处。
for语句

// 范围
for (count in 1..3) {
    println "count: ${count}"
}

// List集合
for (count in [1, 2, 3]) {
    println "count: ${count}"
}

//Map集合
for (entry in ["ker": 21, "john": 22]) {
    println "key is ${entry.key}, value is ${entry.value}"
}

// 字符串
def result = []
for (letter in 'Groovy') {
    result << letter
}
println result.toListString() // [G, r, o, o, v, y]

switch语句

def number = 1234
switch (number) {
    case 111..115:
        println "范围"
        break
    case [11, 12, 13, 14]:
        println "集合"
        break
    case ~"[0-9]{4}":
        println "正则表达式"
        break
}
// 正则表达式

方法

括号可以省略不写

testMethod(1, 2)
// 括号可以省略不写
testMethod 1, 2

def testMethod(a, b) {
    println "a + b = ${a + b}"
}

return可以不写的

Groovy中,定义有返回值的方法时,return语句是可以不写的。当不写return时,Groovy 会把方法执行过程中的最后一行代码作为返回值。

def result = testMethod 2, 3
println result

def testMethod(a, b) {
    "a + b = ${a + b}"
}

闭包是可以作为参数传递的

 // 比较呆板的写法
 def list = [11, 12, 13, 14]
 list.each({
     println it
 })
 
 // Groovy规定,如果方法的最后一个参数是闭包,可以放到方法外面
 list.each() {
     println it
}

// 然后方法可以省略,就变成我们经常看到的样式
list.each {
    println it
}

方法的默认参数

方法通常需要用关键字def声明。默认参数仅能出现在非默认参数之后。

def outInfo(params1, parames2 = 'Groovy') {
    println "params1: ${params1}, parames2: ${parames2}"
}

outInfo("Hello", "Android") //params1: Hello, parames2: Android
// 如果没有传入第二个参数,就会使用默认值
outInfo("Hello")  //params1: Hello, parames2: Groovy

类是创建对象实例的模板。首先定义一个简单描述银行的类,并添加两个属性。接下来创建这个类的对象和显示其属性值的代码。

class Account {
    def id  // 账号
    def amount  // 账户余额
}

//创建对象,使用命名参数模式
def account = new Account(id: "ABC123", amount: 349)
println "Account ${account.id} has amount ${account.amount}"
// Account ABC123 has amount 349
// 上面创建对象的方式等价于下面三行代码
// def account = new Account()
// account.setId("ABC123")
// account.setAmount(349)

def amount = account.getAmount()
println "${amount}"

上面示例隐藏大量信息。首先类Account中的两个属性拥有公共访问权限,其次account.amount所示的属性引用方法实际上是通过account.getAmount()实现的。也就是说,方法getter和方法setter都是Groovy类的隐含方法。

在类中可以定义方法,我们现在为Account类中添加存款和取款的方法。

class Account {
    def id  // 账号
    def amount  // 账户余额
    // 存款
    def add(number) {
        amount += number
    }
    // 取款
    def remove(number) {
        amount -= number
        return number
    }

    @Override
    String toString() {
        return "${id}:${amount}"
    }
}

def account = new Account(id: "ABC123", amount: 10)
println account.toString() // ABC123:10

account.add(100) // 存100
println account.toString() // ABC123:110

account.remove(30) // 取出30
println account.toString() // ABC123:80

在java中,Account类需要有初始化对象的构造器方法,在Groovy中并不需要,只需new操作符和指定参数即可。但是Groovy也有构造器。

实例:设计银行系统建模。一个银行对应多个账户,使用映射处理一对多的关系。其中账户号作为key,Account作为值

class Bank {
    // 银行名称
    def name
    // key : 账号 , value : 账户Account
    def accounts = [:]

    /**
     * 打开或者创建账户
     */
    def openOrCreateAccount(id, amount) {
        def account = this.findAccount(id)
        println account
        if (account != null) {
            account.add(amount)
            return
        }

        account = new Account(id: id, amount: amount)
        accounts[id] = account
    }

    /**
     * 删除账户
     */
    def deleteAccount(id) {
        def account = findAccount(id)
        if (account != null) accounts.remove(id)
    }

    /**
     * 查找账户
     */
    def findAccount(id) {
        if (accounts.isEmpty()) return null

        def acc = accounts.find {
            entry -> return entry.key == id
        }
        return acc != null ? acc.value : null
    }

    /**
     * 获取总资产
     */
    def getTotalAssets() {
        if (accounts.isEmpty()) return 0

        def total = 0
        accounts.each { id, account -> total += account.amount}
        return total
    }
}

元编程

接下来主要 介绍Groovy的元对象协议MOP(Mate-Object-Protocol)的组成部分,MOP是在运行时改变对象和类行为的系统的能力。

元类(MetaClass)的概念

在Groovy中,所有对象都实现了GroovyObject接口,声明在groovy.lang包中

 public interface GroovyObject {
 
     Object invokeMethod(String name, Object args);
 
     Object getProperty(String propertyName);
 
     void setProperty(String propertyName, Object newValue);
 
     MetaClass getMetaClass();

     void setMetaClass(MetaClass metaClass);
}

GroovyObject 与 MetaClass 协作,MetaClass 是Groovy元概念的核心,它提供了一个Groovy类的所有的元数据,也实现了接口MetaObjectProtocol:

Object invokeMethod(Object object, String methodName, Object[] arguments);

Object invokeMethod(Object object, String methodName, Object arguments);

Object invokeStaticMethod(Object object, String methodName, Object[] arguments);

Object invokeConstructor(Object[] arguments);

上面这些方法进行真正的方法调用,使用java反射,GroovyObject 的 invokeMethod方法默认实现转到相应的MetaClass 。MetaClass 被存储在MetaClassRegistry中,通过groovy也从MetaClassRegistry中获取MetaClass 。
在这里插入图片描述

方法调用和拦截

 // MyMetaTest.groovy
 class MyMetaTest {
     def hello() {
         return 'invoked hello directly'
     }
 
     @Override
     Object invokeMethod(String name, Object args) {
         return "invokeMethod: name:${name}, args:${args}"
    }
}

MyMetaTest test = new MyMetaTest()
println test.hello() // invoked hello directly

println test.hello2('kerwin') // invokeMethod: name:hello2, args:[kerwin]

在Groovy中任何对象都实现GroovyObject接口,所以MyMetaTest 也默认实现了GroovyObject接口。如果调用MyMetaTest 中定义了的方法,如:hello,就会直接调用。如果调用MyMetaTest 中未定义方法,如:hello2,如果覆盖了invokeMethod就会执行invokeMethod方法,否则抛出MissingMethodException异常。

 class MyMetaTest implements GroovyInterceptable{
     def hello() {
         return 'invoked hello directly'
     }
 
     @Override
     Object invokeMethod(String name, Object args) {
         return "invokeMethod: name:${name}, args:${args}"
     }
}

MyMetaTest test = new MyMetaTest()

println test.hello() // invokeMethod: name:hello, args:[]

println test.hello2('kerwin') // invokeMethod: name:hello2, args:[kerwin]

println test.&hello() // invoked hello directly

让MyMetaTest 实现GroovyInterceptable接口,该接口是一个标记接口,没有任何方法需要实现。从这个接口的描述可知:实现该接口的类,类中的方法被调用时都会默认使用invokeMethod方法,不管该方法是否已经定义。如果要直接调用已定义的方法,需要使用.&操作符。

 class MyMetaTest2 {
     def greeting = 'accessed greeting directly'
 
     @Override
     void setProperty(String propertyName, Object newValue) {
         println "setProperty>>>> propertyName:${propertyName}, newValue:${newValue}"
         if ('greeting' == propertyName) {
             greeting = newValue
         }
    }

     @Override
     Object getProperty(String propertyName) {
        println "getProperty>>>> propertyName:${propertyName}"
        if (propertyName == 'greeting') return greeting
        return ""
    }
}

MyMetaTest2 test2 = new MyMetaTest2()

println test2.greeting
//getProperty>>>> propertyName:greeting
//accessed greeting directly

test2.greeting = 'Hello Groovy, My is Kerwin.'
println test2.greeting // Hello Groovy, My is Kerwin.
//setProperty>>>> propertyName:greeting, newValue:Hello Groovy, My is Kerwin.
//getProperty>>>> propertyName:greeting
//Hello Groovy, My is Kerwin.

println test2.@greeting
// Hello Groovy, My is Kerwin.

该示例描述了属性的获取特性,如果想直接访问定义的属性,可以使用.@操作符, 对于类中不存在的属性,不要使用.@操作符,否则会抛出MissingFieldException异常。

metaClass

定义一个Person类

/**
 * groovy中默认都是public
 */
class Person implements Serializable {
    String name
    Integer age

    def increaseAge(Integer years) {
        this.age += years
    }

    /**
     * 一个方法找不到时,调用它代替
     * @param name
     * @param args
     * @return
     */
    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"
    }
}

首先定义一个全局管理类ApplicationManager,通过metaClass为Person类注入一个静态方法createPersion

class ApplicationManager {
    def static init() {
       // 开启全局设置
        ExpandoMetaClass.enableGlobally()
        // 注入createPersion 静态方法
        Person.metaClass.static.createPersion = { name, age ->
            new Person(name: name, age: age)
        }
    }
}

调用Person注入的静态createPersion方法

class Entry {
    static void main(def args) {
        ApplicationManager.init()
        Person person = Person.createPersion('kerwin', 123)
        println "name: ${person.name}, age:${person.age}"
        //name: kerwin, age:123
    }
}

File类

下面以示例的方式讲解File类中的方法,其中GDK新增方法有:

  1. void append(Object text) 将字符串text追加到文件末尾
  2. void eachFile(Closure closure) 为指定目录中的每个文件应用闭包
  3. void eachFileRecurse(Closure closure) 为指定目录中的每个文件应用闭包,且对每个子目录使用递归方法
  4. T eachLine(Closure closure) 逐行遍历指定的文档
  5. String getText() 读取文件内容并将它作为一个字符串返回
  6. T withPrintWriter(Closure closure) 为文件创建一个新的PrintWriter辅助方法,并将它传递给闭包,并再次确认问价是否关闭

示例一:以一次一行的方式读取并显示文件

File file = new File('D:\\work\\mycode\\gradle\\groovyLearn\\src\\readme.md')
file.eachLine { String line -> 
    println "line: ${line}"
}

//line: this is the first line.
//line: this is the second line.
//line: this is the third line.
//line: this is the fourth line.

示例二:目录列表,listDir功能可以使用eachFileRecurse实现

def listDir(File dirFile) {
    dirFile.eachFile { File file ->
        println "${file.getAbsolutePath()}"
        if (file.isDirectory()) listDir(file)
    }
}

File dir = new File('D:\\work\\mycode\\gradle\\groovyLearn\\src')
listDir(dir)

示例三:文件拷贝

File srcFile = new File('D:\\work\\mycode\\gradle\\groovyLearn\\src\\readme.md')
println "srcFile is exists: ${srcFile.exists()}"
 
File destFile = new File('D:\\work\\mycode\\gradle\\groovyLearn\\src\\backup.md')
println "destFile is exists: ${destFile.exists()}"
if (!destFile.exists()){
    destFile.createNewFile()
} else {
    destFile.delete()
}

destFile.withPrintWriter { PrintWriter writer ->
    srcFile.eachLine { String line ->
       writer.println(line)
    }
}

实例四:对象的读写

def person = new Person(name: 'kerwin', age: 23)

def file = new File('../../../../person.bin')

if (!file.exists()) file.createNewFile()

// 写入Person对象
file.withObjectOutputStream { outStream ->
   outStream.writeObject(person)
}

// 从文件中读取Person对象
file.withObjectInputStream { inStream ->
   def p = (Person) inStream.readObject()
    println "name:${p.name}, age:${p.age}"
}

json操作详解

实体对象转为json,通过JsonOutput类完成,可以调用prettyPrint方法输出标准json格式

def persons = [new Person(name: 'kerwin', age: 123),
              new Person(name: 'xujinbing', age: 456)]

// 实体对象转化为json
def json = JsonOutput.toJson(persons)
// [{"age":123,"name":"kerwin"},{"age":456,"name":"xujinbing"}]
println json

// prettyPrint方法可以标准json格式输出
println JsonOutput.prettyPrint(json)

json转化为实体对象,通过JsonSlurper类完成

def jsonString = '''
[
    {
        "age": 123,
        "name": "kerwin"
    },
    {
        "age": 456,
        "name": "xujinbing"
   }
]
'''
def jsonSlurper = new JsonSlurper()
def persons = jsonSlurper.parseText(jsonString)
println persons
// [[age:123, name:kerwin], [age:456, name:xujinbing]]

println "name: ${persons[0].name}, age: ${persons[0].age}"
//name: kerwin, age: 123

xml操作详解

首先定一个xml格式的数据

final String xml = '''
   <response version-api="2.0">
        <value>
            <books id="1" classification="android">
                <book available="20" id="1">
                    <title>疯狂Android讲义</title>
                    <author id="1">李刚</author>
                </book>
                <book available="14" id="2">
                   <title>第一行代码</title>
                   <author id="2">郭林</author>
               </book>
               <book available="13" id="3">
                   <title>Android开发艺术探索</title>
                   <author id="3">任玉刚</author>
               </book>
               <book available="5" id="4">
                   <title>Android源码设计模式</title>
                   <author id="4">何红辉</author>
               </book>
           </books>

           <books id="2" classification="web">
               <book available="10" id="1">
                   <title>Vue从入门到精通</title>
                   <author id="4">李刚</author>
               </book>
           </books>
       </value>
    </response>

xml格式数据的解析通过XmlSlurper完成

 def xmlSlurper = new XmlSlurper()
 def response = xmlSlurper.parseText(xml)
 println response.value.books[0].book[0].title.text() // 疯狂Android讲义
 println response.value.books[0].book[0].author.text() // 李刚
 println response.value.books[0].book[0].@available  // 20
 
 // 遍历作者是`李刚`的书籍
 def list = []
 response.value.books.each { books ->
    books.book.each { book ->
        def author = book.author.text()
        if (author == '李刚') list.add(book.title.text())

    }
}
println list.toListString()
// [疯狂Android讲义, Vue从入门到精通]

深度遍历和广度遍历xml数据

/**
  * 深度遍历xml数据
  */
 // 查询作者是`李刚`的书籍
 def books = response.depthFirst().findAll { book ->
     return book.author.text() == '李刚'
 }
 println books.toListString()
 // [疯狂Android讲义李刚, Vue从入门到精通李刚]

/**
 * 广度遍历xml数据
 */
// 查询id=2的书名
def titles = response.value.books.children().findAll { node ->
    node.name() == 'book' && node.@id == '2'
}.collect { node ->
    return node.title.text()
}
println titles.toListString()

生成xml格式数据,通过MarkupBuilder类完成

def file = new File('../../../../data.xml')
if (!file.exists()) file.createNewFile()

FileWriter writer = new FileWriter(file)

/**
 * 写入data.xml文件中数据
 *
 * <books classification='android'>
 *   <book id='1'>
 *     <title>Groovy</title>
 *     <authpr>kerwin</authpr>
 *   </book>
 * </books>
 */
def builder = new MarkupBuilder(writer)
builder.books(classification: 'android') {
    book(id: '1') {
        title('Groovy')
        authpr('kerwin')
    }
}
println response.value.books[0].book[0].title.text() // 疯狂Android讲义
println response.value.books[0].book[0].author.text() // 李刚
println response.value.books[0].book[0].@available  // 20
 
 // 遍历作者是`李刚`的书籍
 def list = []
 response.value.books.each { books ->
    books.book.each { book ->
        def author = book.author.text()
        if (author == '李刚') list.add(book.title.text())

    }
}
println list.toListString()
// [疯狂Android讲义, Vue从入门到精通]

深度遍历和广度遍历xml数据

/**
  * 深度遍历xml数据
  */
 // 查询作者是`李刚`的书籍
 def books = response.depthFirst().findAll { book ->
     return book.author.text() == '李刚'
 }
 println books.toListString()
 // [疯狂Android讲义李刚, Vue从入门到精通李刚]

/**
 * 广度遍历xml数据
 */
// 查询id=2的书名
def titles = response.value.books.children().findAll { node ->
    node.name() == 'book' && node.@id == '2'
}.collect { node ->
    return node.title.text()
}
println titles.toListString()

生成xml格式数据,通过MarkupBuilder类完成

def file = new File('../../../../data.xml')
if (!file.exists()) file.createNewFile()

FileWriter writer = new FileWriter(file)

/**
 * 写入data.xml文件中数据
 *
 * <books classification='android'>
 *   <book id='1'>
 *     <title>Groovy</title>
 *     <authpr>kerwin</authpr>
 *   </book>
 * </books>
 */
def builder = new MarkupBuilder(writer)
builder.books(classification: 'android') {
    book(id: '1') {
        title('Groovy')
        authpr('kerwin')
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值