LeoHe's Space

我们是共产主义接班人

[TOC]

闭包

闭包是一种可执行的代码块的方法,闭包也是对象,可以想方法一样传递参数。由于闭包是代码块,因此也可以在需要时执行。定义闭包的时候可以使用一个或者多个参数。闭包可以访问属性信息,意味着可以访问并修其作用于内的所有变量值。

闭包

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
def greeting = 'hello'
def clos = {
param ->
println "$greeting $param"
}

clos.call("world")

greeting = 'welcome'
clos.call('world')

def demo(clos){
def greeting = 'Bonjour'//this will not affect closure
clos.call('ken')
}

demo(clos) //output welcome ken

以上代码说明,只有闭包被定义且存在,而不是在被调用时,可以访问其状态值。

闭包也常用在集合中。使用闭包可以更高效的遍历元素。

1
2
3
4
5
6
7
1.upto(10,{
println it
})
//或者你也可以这么写
1.upto(10){
println it
}
1
2
3
4
5
6
7
8
//阶乘
def fac = 1
1.upto(5){
num ->
fac *= num
}

println "Factorical : $fac"

闭包与集合和字符串

常用each方法来遍历。

find()返回第一个满足条件的元素,findAll()返回所有满足条件的元素

1
2
3
4
5
6
def list = [1, 2, 3, 4, 5, 6, 7, 8, 9]
list.findAll {
it > 5
}.each {
println it
}

any() and every()

1
2
3
4
5
6
7
8
9
10
11
12
def list = [1, 2, 3, 4, 5, 6, 7, 8, 9]

def anyEle = list.any {
it > 5
}
println anyEle //true

def everyEle = list.every {
it > 5
}

println everyEle //false

collect() and inject

collect用来遍历集合并且返回一个集合

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

def list = [1, 2, 3, 4, 5, 6, 7, 8, 9]

//collect 返回一个由closure转换后的集合
def newlist = list.collect {
it * it
}

println newlist

//collect 的高级范例
def doubles = {
item ->
2 * item
}

def triples = {
item ->
3 * item
}

def isEven = {
item ->
item%2 == 0
}
def map(clos,list){
return list.collect(clos)
}

inject用来遍历集合,首先将需要传递的值和集合项目传递给必报,此时其传递的值将作为处理结果,然后再和下一个集合项目一起传递给闭包,以此类推

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
def factorial = [2, 3, 4, 5]
.inject(1) {
previous, element ->
previous * element
}

println factorial
//=====
def list = [2, 3, 4, 5]
factorial = list.inject(1) {
prviouse, element ->
prviouse * element
}
println factorial
//=====
def list = [2, 3, 4, 5]
def closure = {
previous, element ->
previous * element
}

def factorial = list.inject (1,closure)
println factorial

闭包的其他特性

由于闭包也是一个对象,因此它可以作为方法的参数,同时也可以作为另一个闭包的参数。

作为方法的参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
def filter(list, predict) {
return list.findAll(predict)
}

def isEven = {
x ->
return x % 2 == 0
}

def isOdd = {
x ->
return !isEven(x)
}

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

def evens = filter(table,isEven)
println evens

def odds = filter(table,isOdd)
println odds

作为另一个闭包的参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
def takewhile = {
predicate, list ->
def result = []
for (element in list) {
if (predicate(element)) {
result << element
} else {
return result
}
}
}

def isEven = {
x ->
return x % 2 == 0
}

def isOdd = {
x ->
return !isEven(x)
}

def table1 = [12, 14, 15, 18]
def table2 = [11, 13, 15, 16, 18]

def evens = takewhile.call(isEven,table1)
println evens

作为返回值

1
2
3
4
5
6
7
8
9
10
def multiply(x){
return {
y->
return x * y
}
}

def twice = multiply(2)

println twice(4)

嵌套闭包

选择排序算法的实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
def selection = {
list ->
//交换两个位置的数
def swap = {
sList, p, q ->
def temp = sList[p]
sList[p] = sList[q]
sList[q] = temp
}
//找出最小数位置
def minimumPosition = {
pList, from ->
def mPos = from
def nextFrom = 1 + from
for (j in nextFrom..<pList.size) {
if (pList[j] < pList[mPos]) {
mPos = j
}
}
return mPos
}
def size = list.size() - 1
for (k in 0..<size) {
def minPos = minimumPosition(list, k)
swap(list, minPos, k)
}
return list
}

def table = [13, 14, 12, 11, 14]
def sorted = selection(table)
println sorted

关于闭包的更多信息

闭包和不确定性

先看一个例子,闭包在实参列表外部。如果方法的最后一个参数是闭包,那么可以将它从实参列表中删除,并放在气候的括号后面。

demo(closure) 变为 demo closure

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
def greeting = 'Hello'
def clo = {
param ->
println "$greeting $param"
}

def demo(clos) {
def greeting = 'Bonjour'
clos.call('Ken')
}
//demo()clo error null point exception,解释见后。

demo clo

demo() {
param ->
println "welcome $param"
}


假设clos是一个闭包变量,{...}是一个闭包体。假设xy是两个随意设置的值,则查看如下不同的方法调用语句:

1
2
3
4
method(x,y,{...}) //ok
method(x,y){...} //ok
method(x,y,clos) //ok
method(x,y)clos //error no such method

第二行代码把闭包体直接放在调用参数的后面。第四行代码说明,同样的做法也许不适用闭包变量。Groovy解释器无法忍冬clos标识符是该方法的调用的一部分,因此Groovy解释器就会报错,说明找不到只有具有两个参数的method方法。

如果method标识具有一个参数的方法:

1
2
3
def method(c){
...
}

则clos是个闭包变量,{…}是闭包体,如下调用语句

1
2
3
4
5
6
method({...})  //ok
method(){...} //ok
method{...} //ok
method(clos) //ok
method()clos //error null point exception
method clos //ok

第五行代码中,调用方法时不适用任何实参,在该方法体中,形参将被初始化为null,在这个方法中使用闭包变量将会导致系统错误。

闭包和方法

对于如下两个结构

1
2
3
4
5
6
7
8
def mDouble = {
n ->
return 2*n
}

def mDouble(n){
return 2*n
}

前者是一个闭包定义,后者是一个方法定义。注意,任何闭包引用肯定有固定的作用域。不能出现同名的闭包变量,但是可以出现同名方法(方法重载)

默认参数

像方法一样,闭包也可以被分配带默认值的参数。

1
2
3
4
5
6
7
def greeting = {
msg, name = "leo" ->
println "$msg $name"
}

greeting("hello ")
greeting("hello", "julia")

闭包和作用域

上面讲到的局部闭包实现的选择性排序算法。下面讲一个使用冒泡算法排序。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
def bubbleSort = {
list ->
def swap = {
sList, p, q ->
def temp
temp = sList[q]
sList[q] = sList[p]
sList[p] = temp
}

def size = list.size()
def sortedPosition = 0

while (sortedPosition < size) {
for (index in 1..<(size - sortedPosition)) {
if (list[index] < list[index - 1])
swap(list, index, index - 1)
}
sortedPosition ++
}
return list
}

def list = [1,5,4,6,2,7,0,78,54,23]
bubbleSort(list)
println list

需要注意的是,闭包只能由语句组成,这意味着闭包中不能有方法定义,因此局部闭包swap不能使用方法定义来替代。

递归闭包

用递归的方法求n!

1
2
3
4
5
6
def factorial = {
n ->
return n == 0 ? 1 : n * call(n - 1)
}

println factorial(5)

状态类型

可以使用参数和返回值的动态类型来定义闭包。这个特性使得闭包更加通用。

闭包的动态类型

1
2
3
4
5
6
7
8
9
10
11
12
13
def times = {
x, y ->
return x * y
}

def times = {
x, y ->
return x * y
}

println times(3,4)
println times(3.2,4.15)
println times('hello',4)

在调用时,才确定x和y的类型

1
2
3
12
13.280
hellohellohellohello

静态指定参数类型的闭包

1
2
3
4
def times = {
Number x, Number y ->
return x * y
}

这种方式就限定了闭包的参数类型,不能是Number及其子类意外的其他类型。

有关实参的约定

在调用闭包时,实参的数量必须严格匹配闭包定义时的形参数量。否则,Groovy解释器就会报错,指示参数不正确。

1
2
3
4
5
6
7
8
def clos = {
a,b,c ->
"clos($a$b$c)"
}

clos(1,2,3) //ok
clos(1) //error
clos(1,2,3,4) //error

闭包、集合、范围

列表、映射、和范围等类型提供很多实用闭包作为参数的方法,这有利于迭代处理集合或者范围中的每个元素,并执行指定的任务。

迭代器方法

方法名称 说明
any*
collect*
collect*
each*
every*
find*
findAll*
findIndexOf*
inject*
reverseEach*
sort*

Return 语句

我们先看一个代码范例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
def isMemberA(item, list) {
def size = list.size()
for (index in 0..<size) {
if (list[index] == item)
return true
}
return false
}

def isMemberB(item, list) {
list.each {
if (it == item)
return true
}
return false
}
def numbers = [11, 12, 13, 14]

println isMemberA(15,numbers)
println isMemberA(13,numbers)

println isMemberB(15,numbers)
println isMemberB(13,numbers) //这个有问题

输出为:

1
2
3
4
false
true
false
false

显然,Groovy方式的返回是有问题的。这个是为什么呢?

在我们的范例中,作为实参的闭包包含一个return语句。闭包返回true,就会导致while循环继续处理列表中的下一个项目。

可以看一下each的实现方式

1
2
3
4
5
6
7
8
public static <T> Iterator<T> each(Iterator<T> self, @ClosureParams(FirstGenericType.class) Closure closure) {
while(self.hasNext()) {
Object arg = self.next();
closure.call(arg);
}

return self;
}

可以看到return是指返回的closure.call()但是外层循环还是在的,并不能直接终止循环,因此最终所有的数都会跑一边,这个是一个比较需要注意的地方,在使用each进行迭代的时候一定要注意哦!

高级闭包

假设某家超时使用特殊的商品价格来吸引新客户,以及维护已经有的客户。在一周内,也许早餐谷类视频的价格优惠1/3;在另一周内,优惠政策可能变化,化妆品销售按照买一送一的方式进行销售。在这种优惠政策经常变化的环境中,需要某种灵活的方式来定义这些“业务规则”,并且能够灵活的修改和调整。

此处的思路来源多范式编程方式,这是Groovy的一个非常重要的特性。借助更高级的函数,函数编程范式综合了多台、组合以及计算模式。

简单闭包

以前,把闭包描述为一个代码块。闭包实现参数化更加有价值,可以被引用,可以被当做参数传入方法,以及可以与call消息一起被调用。

范例:计算两个数值参数的乘积

1
2
3
4
5
6
7
def multiply = {
x, y ->
return x * y
}

println "multiply(3,4) = ${multiply(3,4)}"
println "multiply(3.4,5.6) = ${multiply(3.4,5.6)}"

由于闭包可以引用对象的状态。

范例:作用域和闭包

1
2
3
4
5
6
7
8
9
10
11
12
def multiplier = 2 
def multiply = {
x ->
return x * multiplier
}
println "multiply(3) = ${multiply(3)}"
println "multiply(5.6) = ${multiply(5.6)}"

multiplier = 3

println "multiply(3) = ${multiply(3)}"
println "multiply(5.6) = ${multiply(5.6)}"

在闭包定义范围内的变量可以从闭包代码内访问。

闭包是可以将另一个闭包返回的,类似一个二阶的函数

范例:闭包返回值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
def add = {
x, y ->
return x + y
}

def subtract = {
x, y ->
return x - y
}

def multiply = {
x, y ->
return x * y
}

def divide = {
x, y ->
return x / y
}

def arithmetic = {
arith ->
switch (arith) {
case 'ADD':
return add
case 'SUBTRACT':
return subtract
case 'MULTIPLY':
return multiply
case 'DIVIDE':
return divide
default:
return add
}
}

def addOperation = arithmetic('ADD')
def mulOperation = arithmetic('MULTIPLY')

println "addOperation(3,4) = ${addOperation(3,4)}"
println "mulOperation(3,4) = ${mulOperation(3,4)}"
println arithmetic('DIVIDE')(3,4)

部分应用

在上面的范例2中,multiply闭包计算一个参数和内部变量multiplier的乘积。现在,如果把multiplier作为闭包的参数,这个闭包返回multiply闭包,后者计算自己的参数与multiplier的成绩,这就是闭包部分应用的范例。

范例:partial applicaiton

1
2
3
4
5
6
7
8
9
10
11
12
def multiply = {
x, y ->
return x * y
}

def tripple = multiply.curry(3)
def quadroup = multiply.curry(4)

def twelve = multiply.curry(3,4)
println tripple(3)
println quadroup(5)
println twelve()

**注意:表达式multiply.curry(3)**表示把参数乘以3的闭包。然后,被返回的闭包使用一个参数进行调用。在数学家Haskell Curry之后,闭包的部分应用被称为修正。闭包triple的定义如下:

1
2
3
4
def triple = {
y ->
return 3 * y
}

其中,第一个参数被删除,所有出现第一个参数的地方使用数字3来替代。

加法和惩罚被称为可交换操作。这意味着A+B = B+A and A*B = B*A。但是减法和除法是不可交换的操作。我们可以事件类似于multiply的闭包。

范例:实现可交换操作

1
2
3
4
5
6
7
8
9
10
11
12
13
def rSubtract = {
y, x ->
return x - y
}
def lSubtract = {
x, y ->
return x - y
}
def subtract10 = rSubtract.curry(10)
def subractFrom20 = lSubtract.curry(20)

println subtract10(22)
println subractFrom20(14)

关于修正闭包的一个重点是:curry方法的实参数量绝对不能超过该必报所需的实参实际数量。比如该闭包有有三个参数,则调用curry方法时,可以使用零个,一个,两个,或者三个实参。

部分应用可以被认为是一种简化形式,是个复杂任务可以被分解为多个子任务。

组合

结构化顺序的一种组织方式是顺序执行多个任务。通常而言,每部分任务被分别设计和实现。在此,也许认为,一个闭包标识需要执行的某个简单任务。适用组合概念进行任务合并,可以非常容易构造出复杂的任务。并且,借助不同的祝贺方式,可以较快的创建超时所需要的新任务,本文开篇所述案例。

范例:闭包组合

范例演示了composition闭包,这个闭包有两个参数f和g,两个参数代表闭包,把闭包g应用到x比如g(x),把闭包f应用到处理结果比如(fg(x))

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
def composition = {
f, g, x ->
println "f:${f.class} g: ${g.class} x: ${x.class}"
return f(g(x))
}

def composition2 = {
f, g, x ->
return f(x) * g(x)
}

def multiply = {
x, y ->
return x * y
}

def triple = multiply.curry(3)
def quadroup = multiply.curry(4)

def twelveTimes = composition.curry(triple, quadroup)
def multiplyCompo = composition2.curry(triple,quadroup)

println "twelveTimes(12) = ${twelveTimes(12)}" //(3*(4 * 12)) = 144
println "multiplyCompo(12) = ${multiplyCompo(12)}" // 3*12 * 4*12 = 1728

闭包triple有一个参数,并把这个参数与3相乘。闭包twelveTimes是闭包tripe和quadroup的组合。结果是,闭包twelveTimes把参数乘以4,然后把乘法结果与3相乘。

范例:组合与集合

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
def composition = {
f, g, x ->
return f(g(x))
}

def multiply = {
x, y ->
return x * y
}

def triple = multiply.curry(3)
def quadroup = multiply.curry(4)

def twelveTimes = composition.curry(triple,quadroup)

def table = [1,2,3,4].collect{
element ->
return twelveTimes(element)
}
println "tabl = $table "

计算模式

介绍一种机制,是的闭包包含一种计算模式。

范例是对列表中的每个元素按照某中形式进行转换。这种转换通常被命名为map

范例:映射

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
//map closure
def map = {
clos, list ->
return list.collect(clos)
}

//composition closure
def compositon = {
f ,g ,x ->
return f(g(x))
}

def multiply = {
x , y ->
return x * y
}

def triple = multiply.curry(3)
def quadroup = multiply.curry(4)

def tripleAll = map.curry(triple)


def table = tripleAll([1,2,3,4])
println table

分析:

1
2
3
4
5
6
7
8
9
//实际的trpleAll将是一个CurryedClosure
//quadroup的结构
quadroup = {
list ->
return list.collect{
element ->
return 3 * element
}
}

关于映射的一个等级模式是,如果把闭包(比如f)映射到列表x,然后把闭包(比如g)应用到前一个闭包的处理结果。那么上述过程就等价于把g和f的组合应用到这个列表x。

范例:处理等价关系

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
//map closure
def map = {
clos, list ->
return list.collect(clos)
}

//composition closure
def compositon = {
f, g, x ->
return f(g(x))
}

def multiply = {
x, y ->
return x * y
}

def triple = multiply.curry(3)
def quadroup = multiply.curry(4)

def composeMapMap = compositon.curry(map.curry(triple), map.curry(quadroup))

def table = composeMapMap([1,2,3,4]) // each element * 4 * 3

println table

业务规则

现在我们考虑如何计算一个Book的净价,在计算过程要考虑到上品折扣和政府税金,比如增值税。

关于业务规则的实际实现

范例:净价计算

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
class Book {
def name
def author
def price
def category
}

def bk = new Book(name: 'Groovy', author: 'KenB', price: 25, category: 'CompSci') // 参数列表初始化

def discountRate = 0.1
def taxRate = 0.17

def rMultiply = {
y , x ->
return x * y
}

def lMultiply = {
x , y ->
return x * y
}

def composition = {
f,g,x ->
return f(g(x))
}

def calDiscounteedPrice = rMultiply.curry(1-discountRate)
def calTax = rMultiply.curry(1 + taxRate)
def calcNetPrice = composition.curry(calTax,calDiscounteedPrice)

def netPrice = calcNetPrice(bk.price)

println "netPrice = ${netPrice}"

范例:有上限折扣

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
class Book {
def name
def author
def price
def category
}

def bk = new Book(name: 'Groovy', author: 'KenB', price: 25, category: 'CompSci')

def discountRate = 0.1
def taxRate = 0.17
def maxDiscount = 3

def rMultiply = {
y, x ->
return x * y
}

def lMultiply = {
x, y ->
return x * y
}

def subtract = {
x, y ->
return x - y
}

def rSubtract = {
y, x ->
return x - y
}

def lSubtract = {
x, y ->
return x - y
}

def min = {
x, y ->
return x < y ? x : y
}

def id = {
x ->
return x
}

def composition = {
f, g, x ->
return f(g(x))
}

def bComposition = {
h, f, g, x ->
return h(f(x), g(x))
}

def calcDiscount = rMultiply.curry(discountRate)
def calcActuralDiscount = bComposition.curry(min, calcDiscount, id)
def calcDiscountedPrice = bComposition.curry(subtract, id, calcActuralDiscount)
def calcTax = rMultiply.curry(1 + taxRate)
def calcNetPrice = composition.curry(calcTax,calcDiscountedPrice)

println "bk.price : $bk.price"

def netPrice = calcNetPrice(bk.price)

println "netprice = $netPrice"

打包

以上范例开发了大量非常有价值的闭包,可以非常灵活的合并使用。因此,有必要把这些闭包都打包到一个类中去,这样更加容易引入到应用程序。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
abstract class Functor {
public static Closure bAdd = {
x, y -> return x + y
}

public static Closure rAdd = {
y, x -> return x + y
}

public static Closure bSubtract = {
x , y -> return x - y
}
//blabla
}

这个类包含了算数的闭包。

闭包与协程

调用一个函数或方法会在程序的执行序列创建一个新的作用域.我们会在一个入口点进入函数.在方法完成之后,回到调用者的作用域.

协程(Coroutine)则支持多个入口点,每个入口点都是上次挂起调用的位置.我们可以进入一个函数,执行部分代码,挂起,在回到调用者的上下文或作用域内执行一些代码.之后我们可以在挂起的地方恢复该函数的执行.

写成对于实现某些特殊的逻辑或算法非常方便,比如用着生产者-消费者问题中.生产者会接受一些输入,对输入做一些厨师处理,通知消费者拿走处理过的值并做进一步计算,并输出或存出结果.消费者处理它那部分工作,完成之后通知生产者以获取更多输入.

在Java中,wait() and notify()与多线程借个使用,可以实现协程.闭包会让人产生”协程是在一个线程中执行”的感觉or错觉.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
def iterate(n, closure){
1.upto(n){
println "In iterate with value ${it}"
closure(it)
}
}

println "Calling iterate"
total = 0
iterate(4){
total += it
println "In closure total so far is ${total}"
}
println "Done"
/**
每次调用闭包,我们都会从上一次调用中回复total的值. //可是我并没有从例子中看出什么是协程????
*/

协程不是进程或线程,其执行过程更类似于子例程,或者说不带返回值的函数调用。 协程还是比较复杂,嗯后续在研究研究.

闭包委托

Groovy的闭包支持方法委托,而且提供了方法分派能力.

this,owner and delegate是闭包的三个属性,用于确定有那个对象处理闭包内的方法调用,一般而言,delegate 会被设置为 owner,但是对其进行修改,可以有非常好的元编程能力.

利用闭包委托,可以更好的写出DSL,将在DSL好好的讲述本内容.

题外话

有关函数式编程,可参考阮一峰老师的博客,在这里引用阮一峰老师的博客,特此注明,并非常感谢老师能够出一片教程给大家科普一下。

函数式编程入门教程–阮一峰

ps:自己的一些理解

对于函数式编程中的柯里化,对应高中数学的只是,其实就是函数的一个组合。为了组合函数,需要将多个多参数函数转换成单参数函数。

举个🌰:
$$
z = x + y
$$

$$
z = x * y
$$

如果我想得到一个复合函数使得2->映射到8,计算过程为2 * 3 + 2

那么我怎么得到这个函数呢?定义这个映射为f
$$
f(x) = x * 3 + 2
$$
那么在程序中我们怎么体现呢

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
//这里的函数都是以闭包形式表示的准确来说这不是一个"方法",而是一个闭包的对象
//首先我们需要一个加法函数,这个加法函数是一个广义的
def add = {
x, y ->
return x + y
}
//然后我们需要一个乘法函数,同样是一个广义的
def multiple = {
x, y ->
return x * y
}
//然后我们定义一个组合,这个组合是嵌套函数,两个映射f,g和一个自变量x
def compose = {
f, g, x ->
return f(g(x))
}

//add.curry(2) : y = 2 + z
//multiple.curry(2) : z = 3 * x 注:这里只是为了方便,变量名并不代表带入关系
//compose.curry(add2,multiple3) = : z = 2 + (3 * x)
def add2 = add.curry(2)
def multiple3 = multiple.curry(3)

def addAndMultiple = compose.curry(add2,multiple3)
println addAndMultiple(2)

[TOC]

概述

Groovy特点

  • 同时支持静态与动态类型
  • 支持运算符重载
  • 对于正则表达式的本地支持
  • 对xml和html的原生支持
  • 与Java无缝衔接

安装

可以参考Groovy官网上的方式,利用SDK来安装Groovy。

基本语法

Helloword

1
2
3
4
5
6
7
8
9
//类似Java的方式
class HelloWorld{
static void main(String[] args){
//Using a simple printn statement to print output to console.
println('hello world);
}
}
//如果用Groovy脚本的方式:
println 'hello world'

导入

1
2
import groovy.xml.MarkupBuilder
def xml = new MarkupBuilder()

默认情况下,Groovy在代码中包括了一些库,不需要显示的导入。

1
2
3
4
5
6
7
8
9
10
java.lang.*;
java.util.*;
java.io.*;
java.net.*;

groovy.lang.*;
groovy.util.*;

java.math.BigInteger;
java.math.BigDecimal;

Groovy语句

语句可以是一个关键字,一个标识符,常量,字符串文字或者是符号

注释

参考Java的就可以

分号

Groovy可以不用写分号一行就是一条语句

声明

Groovy声明变量的方式是利用def 关键字。标识符以字母,美元或者下划线开头。

1
2
3
def employee
def student
def conter

字符

浮点数,整数,字符和字符串

1
2
3
4
5
def i = 12
def j = 12.5
def a = 'a'
def s1 = "aaa"
def s2 = 'aaaaaaa'

数据类型

内置数据类型

  • byte
  • short
  • int
  • long
  • float
  • double
  • char
  • boolean
  • String

数字类(Number)

  • Byte
  • Short
  • Integer
  • Long
  • Float
  • Double
  • BigInteger
  • BIgDecimal

变量

变量的声明有两种:

一种是类似Java的方式声明,一种是利用def关键字,变量的类型在赋值的时候去确定。

1
2
3
4
5
6
7
String a = "hello"
def a = 'hello'
//
def a = 1
println(a.getClass())
a = new String("1112234")
println(a.getClass())

//利用def的方式,变量的类型还可以随时动态的切换

运算符

  • 算术运算符
  • 关系运算符
  • 逻辑运算符
  • 位运算符
  • 赋值运算符

主要介绍一下Groovy比Java多出的一些特性

范围运算符

1
2
3
def range = 0..5
println(range.class)
println(range)

输出

1
2
class groovy.lang.IntRange
[0, 1, 2, 3, 4, 5]

流程控制

循环

while

1
2
3
while(condition){
//execution
}

for

1
2
3
4
5
6
7
8
9
10
11
12
13
14
for(;;){
//excution
}

//for in
for(var in range){
//
}

//for 语句也可以用于循环访问map,将输出一个键值
def e = ["a":12,"b":31,"c":12,"d":32]
for (a in e){
println(a)
}

break

终止内层循环

continue

当执行continue语句时,控制立即传递到最近的封闭循环的测试条件,以确定循环是否应该继续。对于该特定循环迭代,循环体中的所有后续语句都将被忽略。

条件

if

if-else

switch

方法

Groovy用def关键字定方法。方法可以接受任意数量参数,可不用显示定义类型。可以添加修饰符,默认的是public的。

1
2
3
def methodName(parm1, param2, param3){
//code
}

参数

可以添加任意多个参数,并且可以指定默认参数,但是默认参数必须定义在参数列表的末尾。

1
2
3
def someMethod(param1, param2. param3 = 3){
//method code
}

返回值

1
2
3
4
5
6
def add (x, y, z = 3, w = 4){
x + y + z + w;
}

s = add(1,2)
println(s)

上面的代码,默认的返回最后一行表达式的值。

但是为了避免歧义:我们可以通过这种方式来进行:

1
2
3
4
5
6
7
8
9
def int add(x ,y){  //定义返回值类型。
x + y
}

//or
def add(x, y){
return x + y
}
//通过以上两种方式可以明确这个方法是否有返回值,这样避免歧义。

文件I/O

Groovy在使用IO时提供了许多辅助方法。Groovy提供了更简单的类来为文件提供以下功能:

  • 读取文件
  • 写入文件
  • 遍历文件树
  • 读取和写入数据对象到文件

也可以使用java.io.*下面的所有标准Java类

读取文件

1
2
3
new File("/Users/hefuduo/hefuduo.profile").eachLine {
line -> println line
}

如果要将文件的整个内容作为字符串获取,可以使用文件类的text属性。

1
2
File fi = new File("/Users/hefuduo/hefuduo.profile")
println fi.text

或者使用Java提供的InputStream

1
2
3
4
5
File fi = new File("/Users/hefuduo/hefuduo.profile")
InputStream it = new FileInputStream(fi)
it.eachLine {
println it
}

写入文件

1
2
3
4
5
File fi = new File("/Users/hefuduo/hefuduo.profile")
fi.withWriter {
it.append("Job : computer engineer")
}
println fi.text

以追加的形式写入文件:

1
2
3
4
5
6
7
8
9
File fi = new File("/Users/hefuduo/hefuduo.profile")
OutputStream stream = new FileOutputStream(fi,true)
stream.withWriter {
it.write("age:27")
}
stream.close()
fi.eachLine {
println it
}

获取文件大小

1
2
3
File fi = new File("/Users/hefuduo/hefuduo.profile")
println fi.absolutePath
println fi.length()

测试文件是否是目录

1
2
println fi.isFile()
println fi.isDirectory()

创建与删除目录

1
2
3
File fi = new File("/Users/hefuduo/dirtest/")
fi.mkdir()
fi.delete()

复制文件

1
2
3
4
File fi = new File("/Users/hefuduo/hefuduo.profile")
def src = fi
def dst = new File(fi.absolutePath)
dst << src.text

获取目录中的所有内容

1
2
3
4
5
6
File fi = new File("/Users/hefuduo/")
def files = fi.listFiles()
println files.length
files.each {
println it.name
}

如果要递归的显示目录与子目录中的所有文件,则可以使用File了eachFileRecurse函数。

1
2
3
4
5
File fi = new File("/Users/hefuduo/")
fi.eachFileRecurse {
file ->
println file.getAbsolutePath()
}

Groovy 可选

Groovy是个动态类型语言,相对Java的强类型语言,Groovy在编写代码时候,可以灵活的提供类型或不是类型。

Groovy内部数据

Groovy数字

在Groovy中,数字实际上标识为对象,他们都是类Integer的一个实例。

Groovy支持整数和浮点数

1
2
Integer x = 5
Float y = 1.25

装箱与拆箱

1
2
3
def x = 5, y = 6, z = 0
z = x + y
println z

Groovy字符串

Groovy提供了多种表示String字面量的方法。 Groovy中的字符串可以用单引号(’),双引号(“)或三引号(”“”)括起来。此外,由三重引号括起来的Groovy字符串可以跨越多行。

字符串索引

1
2
3
4
5
6
String sample = "Hello Groovy"
println sample[4]
println sample[-1]
println sample[1..2]
println sample[4..2]
println sample[-2]

Groovy范围

范围是指定值序列的速记。范围由序列中的第一个和最后一个值表示,Range可以是包含或排除。包含范围包括从第一个到最后一个的所有值,而独占范围包括除最后一个之外的所有值。这里有一些范例文字的例子 -

  • 1..10 - 包含范围的示例
  • 1 .. <10 - 独占范围的示例
  • ‘a’..’x’ - 范围也可以由字符组成
  • 10..1 - 范围也可以按降序排列
  • ‘x’..’a’ - 范围也可以由字符组成并按降序排列。

Groovy列表

列表是用于存储数据项集合的结构。在Groovy中,List保存了一系列对象引用。List中的对象引用占据序列中的位置,并通过整数索引来区分。列表文字表示为一系列用逗号分隔并用方括号括起来的对象。

要处理列表中的数据,我们必须能够访问各个元素。 Groovy列表使用索引操作符[]索引。列表索引从零开始,这指的是第一个元素。

以下是一些列表的示例 -

  • [11,12,13,14] - 整数值列表
  • [‘Angular’,’Groovy’,’Java’] - 字符串列表
  • [1,2,[3,4],5] - 嵌套列表
  • [‘Groovy’,21,2.11] - 异构的对象引用列表
  • [] - 一个空列表

Groovy映射

映射(也称为关联数组,字典,表和散列)是对象引用的无序集合。Map集合中的元素由键值访问。 Map中使用的键可以是任何类。当我们插入到Map集合中时,需要两个值:键和值。

以下是一些映射的例子 -

  • [‘TopicName’:’Lists’,’TopicName’:’Maps’] - 具有TopicName作为键的键值对的集合及其相应的值。
  • [:] - 空映射。

在本章中,我们将讨论Groovy中可用的映射方法。

1
2
3
4
5
def aMap = ['TopicName':'Lists','TopicName2':'Maps']
aMap.each {
key,value ->
println("key = $key" + "value = $value")
}

Groovy正则表达式

正则表达式是用于在文本中查找子字符串的模式。 Groovy使用〜“regex”表达式本地支持正则表达式。引号中包含的文本表示用于比较的表达式。

例如,我们可以创建一个正则表达式对象,如下所示 -

1
def regex = ~'Groovy'

当Groovy运算符=〜在if和while语句(见第8章)中作为谓词(返回布尔值的表达式)出现时,左侧的String操作数与右侧的正则表达式操作数匹配。因此,以下每个都传递值true。

当定义正则表达式时,可以使用以下特殊字符

  • 有两个特殊的位置字符用于表示一行的开始和结束:caret(∧)和美元符号($)。
  • 正则表达式也可以包括量词。加号(+)表示一次或多次,应用于表达式的前一个元素。星号(*)用于表示零个或多个出现。问号(?)表示零或一次。
  • 元字符{和}用于匹配前一个字符的特定数量的实例。
  • 在正则表达式中,句点符号(。)可以表示任何字符。这被描述为通配符。
  • 正则表达式可以包括字符类。一组字符可以作为简单的字符序列,包含在元字符[和]中,如[aeiou]中。对于字母或数字范围,可以使用[a-z]或[a-mA-M]中的短划线分隔符。字符类的补码由方括号内的前导插入符号表示,如[∧a-z]中所示,并表示除指定的字符以外的所有字符。
1
2
3
4
5
6
7
'Groovy' =~ 'Groovy' 
'Groovy' =~ 'oo'
'Groovy' ==~ 'Groovy'
'Groovy' ==~ 'oo'
'Groovy' =~ '∧G'
‘Groovy' =~ 'G$'
‘Groovy' =~ 'Gro*vy' 'Groovy' =~ 'Gro{2}vy'
1
2
def regex = ~'groovy'
println ("groovy".matches(regex))

面向对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Empire{
private String name //必须制定getter和setter,因为是private的
int age //提供默认的getter 和setter
Empire(String name,int age){
this.name = name
this.age = age
}
def getName(){
return name
}
}

Empire empire = new Empire("China",5000)
println empire.getName() //属性getter
println empire.name //属性
println empire.age

继承&扩展

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
interface FLY{
def fly()
}

class Animal {
private String name
private String category

Animal(String name, String category) {
this.name = name
this.category = category
}

String getName() {
return name
}

String getCategory() {
return category
}

void setName(String name) {
this.name = name
}

void setCategory(String category) {
this.category = category
}
}

class Dove extends Animal implements FLY{


Dove(String name, String category) {
super(name, category)
}

@Override
def fly() {
println "I believe i can fly"
}
}

Dove d = new Dove("Dove","Bird")
println d.name
println d.category
d.fly()

Dove f = new Dove("","")
f.name = "lala"
f.category = "prettyBird"
println f.name
println f.category

内部类

下面是一个外部和内部类的例子。在下面的例子中,我们做了以下事情 -

  • 创建一个名为Outer的类,它将是我们的外部类。
  • 在Outer类中定义名为name的字符串。
  • 在我们的外类中创建一个内部或嵌套类。
  • 请注意,在内部类中,我们可以访问在Outer类中定义的名称实例成员。

其实Groovy中的内部类和Java中的是一样的

抽象类

同上

1
2
3
4
5
6
7
8
9
10
11
abstract class ABTest{
abstract def test()
}

class AB extends ABTest{

@Override
def test() {
return null
}
}

接口

接口定义了类需要遵守的契约。接口仅定义需要实现的方法的列表,但是不定义方法实现。需要使用interface关键字声明接口。接口仅定义方法签名。接口的方法总是公开的。在接口中使用受保护或私有方法是一个错误。

Groovy 泛型

可以参考Java的泛型,他们是一样的。(因为Groovy也是基于JVM)

Groovy特征

特征是语言的结构构造,允许 -

  • 行为的组成。
  • 接口的运行时实现。
  • 与静态类型检查/编译的兼容性

它们可以被看作是承载默认实现和状态的接口。使用trait关键字定义trait。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55

trait SwimmingAbility{
def swim(){
println "${this.class} is swimming"
}
}

trait FlyingAbility{
def fly(){
println "${this.class} is flying"
}
}
interface Eat{
def eat()
}

class Duck implements SwimmingAbility ,FlyingAbility,Eat{

@Override
def eat() {
return null
}
}

Duck d = new Duck()

d.fly()
d.swim()

trait Name implements FlyingAbility, SwimmingAbility{
public String name
abstract def printName()
}

class Person implements Name{
public String name

@Override
def printName() {
println Name__name
}
}
//1.解决钻石问题(多重继承引发的歧义问题),同时能够实现多重继承的好处
//2.对trait的理解就是嵌入实现类中,而不是继承的那种上下文的关系
Person p = new Person()
p.Name__name = "hefuduo"
p.name = "LeoHe"
p.swim()
p.fly()

def foo(String str){
str?.reverse()
}

println foo(null)

##实现接口

Traits可以实现接口,在这种情况下,使用implements关键字声明接口。

属性

特征可以定义属性。下面给出了具有属性的trait的示例。

在以下示例中,integer类型的Marks1是一个属性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
class Example {
static void main(String[] args) {
Student st = new Student();
st.StudentID = 1;

println(st.DisplayMarks());
println(st.DisplayTotal());
}

interface Total {
void DisplayTotal()
}

trait Marks implements Total {
int Marks1;

void DisplayMarks() {
this.Marks1 = 10;
println(this.Marks1);
}

void DisplayTotal() {
println("Display Total");
}
}

class Student implements Marks {
int StudentID
}
}

行为的构成

特征可以用于以受控的方式实现多重继承,避免钻石问题。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
trait Name implements FlyingAbility, SwimmingAbility{
public String name
abstract def printName()
}

class Person implements Name{
public String name

@Override
def printName() {
println Name__name
}
}
//1.解决钻石问题(多重继承引发的歧义问题),同时能够实现多重继承的好处
//2.对trait的理解就是嵌入实现类中,而不是继承的那种上下文的关系
Person p = new Person()
p.Name__name = "hefuduo"
p.name = "LeoHe"
p.swim()
p.fly()

Groovy闭包

闭包是一个短的匿名代码块。它通常跨越几行代码。一个方法甚至可以将代码块作为参数。它们是匿名的。

下面是一个简单闭包的例子,它是什么样子。

1
2
3
4
def close = {
println "hello groovy"
}
close.call()

闭包中的形式参数

闭包也可以包含形式参数,以使它们更有用,就像Groovy中的方法一样。

1
2
3
4
5
6
7
8
def closure = {
suffix ->
println "hello $suffix"
}

//如果不写参数suffix,默认的提供一个叫it的参数

closure.call("world")

闭包和变量

更正式地,闭包可以在定义闭包时引用变量。以下是如何实现这一点的示例。

1
2
3
4
5
6
7
8
9
10
11
def closure = {
param ->
println param
}


def fin(String logan,Closure s){
s.call(logan)
}

fin("hefuduo",closure)

集合和字符串中的闭包

几个List,Map和String方法接受一个闭包作为参数。让我们看看在这些数据类型中如何使用闭包的例子。

使用闭包和列表

以下示例显示如何使用闭包与列表。在下面的例子中,我们首先定义一个简单的值列表。列表集合类型然后定义一个名为.each的函数。此函数将闭包作为参数,并将闭包应用于列表的每个元素

使用映射闭包

以下示例显示了如何使用闭包。在下面的例子中,我们首先定义一个简单的关键值项Map。然后,映射集合类型定义一个名为.each的函数。此函数将闭包作为参数,并将闭包应用于映射的每个键值对。

Groovy注解

注释是元数据的形式,其中它们提供关于不是程序本身的一部分的程序的数据。注释对它们注释的代码的操作没有直接影响。

注释主要用于以下原因 -

  • 编译器信息 -编译器可以使用注释来检测错误或抑制警告。
  • 编译时和部署时处理 -软件工具可以处理注释信息以生成代码,XML文件等。
  • 运行时处理 -一些注释可以在运行时检查。

字符串类型

1
2
3
4
@interface 
Simple{
String str1() default "hi"
}

枚举类型

1
2
3
4
enum DayOfWeek{Mon, Tue, Wed, Thu, Sat, Sun}
@interface Scheduled{
DayOfWeek dayOfWeek()
}

类类型

1
2
3
4
5
6
7
@interface 
Simple{}

@Simple
Class User{
//...
}

注释成员值

使用注释时,需要至少设置所有没有默认值的成员。下面给出一个例子。当定义后使用注释示例时,需要为其分配一个值。

关闭注释参数

Groovy中注释的一个很好的特性是,你也可以使用闭包作为注释值。因此,注释可以与各种各样的表达式一起使用。

下面给出一个例子。注释Onlyif是基于类值创建的。然后注释应用于两个方法,它们基于数字变量的值向结果变量发布不同的消息。

1
2
3
4
5
6
7
8
9
10
11
12
13
@interface OnlyIf {
Class value()
}

@OnlyIf({ number<=6 })
void Version6() {
result << 'Number greater than 6'
}

@OnlyIf({ number>=6 })
void Version7() {
result << 'Number greater than 6'
}

元注释

这是groovy中注释的一个非常有用的功能。有时可能有一个方法的多个注释,如下所示。有时这可能变得麻烦有多个注释。

1
2
3
@Procedure 
@Master class
MyMasterProcedure {}

在这种情况下,您可以定义一个元注释,它将多个注释集中在一起,并将元注释应用于该方法。所以对于上面的例子,你可以使用AnnotationCollector来定义注释的集合。

1
2
3
4
5
import groovy.transform.AnnotationCollector

@Procedure
@Master
@AnnotationCollector

一旦完成,您可以应用以下元注释器到该方法 -

1
2
3
4
5
6
7
8
import groovy.transform.AnnotationCollector

@Procedure
@Master
@AnnotationCollector

@MasterProcedure
class MyMasterProcedure {}

Groovy XML

XML 生成

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import groovy.xml.MarkupBuilder

def mB = new MarkupBuilder()

// Compose the builder
mB.collection(shelf: 'New Arrivals') {
movie(title: 'Enemy Behind')
type('War, Thriller')
format('DVD')
year('2003')
rating('PG')
stars(10)
description('Talk about a US-Japan war')
}

mB.Html(){
header(ref : "www.baidu.com")
body{
h1("hello world")
}
}

XML解析

Groovy XmlParser类使用一个简单的模型来将XML文档解析为Node实例的树。每个节点都有XML元素的名称,元素的属性和对任何子节点的引用。这个模型足够用于大多数简单的XML处理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
<collection shelf="New Arrivals">

<movie title="Enemy Behind">
<type>War, Thriller</type>
<format>DVD</format>
<year>2003</year>
<rating>PG</rating>
<stars>10</stars>
<description>Talk about a US-Japan war</description>
</movie>

<movie title="Transformers">
<type>Anime, Science Fiction</type>
<format>DVD</format>
<year>1989</year>
<rating>R</rating>
<stars>8</stars>
<description>A schientific fiction</description>
</movie>

<movie title="Trigun">
<type>Anime, Action</type>
<format>DVD</format>
<year>1986</year>
<rating>PG</rating>
<stars>10</stars>
<description>Vash the Stam pede!</description>
</movie>

<movie title="Ishtar">
<type>Comedy</type>
<format>VHS</format>
<year>1987</year>
<rating>PG</rating>
<stars>2</stars>
<description>Viewable boredom</description>
</movie>

</collection>

解析代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
def parser = new XmlParser()
def doc = parser.parse("assets/movies.xml")
doc.movie.each {
bk ->
print "Movie Name : "
println "${bk['@title']}"
print "Movie Type : "
println "${bk.type[0].text()}"
print "Movie Format : "
println "${bk.format[0].text()}"
print "Movie year : "
println "${bk.year[0].text()}"
print "Rating : "
println "${bk.rating[0].text()}"
print "Starts : "
println "${bk.stars[0].text()}"
print "Description : "
println "${bk.description[0].text()}"
println "============================="
}

重要的事情需要注意上面的代码。

  • 正在形成类XmlParser的对象,以便它可以用于解析XML文档。
  • 解析器被给定XML文件的位置。
  • 对于每个电影元素,我们使用闭包浏览每个子节点并显示相关信息。

对于movie元素本身,我们使用@符号显示附加到movie元素的title属性。

Groovy JMX

JMX 用于监控与Java虚拟机环境有任何关系的所有应用程序。

监视JVM

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
import java.lang.management.*

def os = ManagementFactory.operatingSystemMXBean
println """OPERATING SYSTEM:
OS architecture = $os.arch
OS name = $os.name
OS version = $os.version
OS processors = $os.availableProcessors
"""

def rt = ManagementFactory.runtimeMXBean
println """RUNTIME:
Runtime name = $rt.name
Runtime spec name = $rt.specName
Runtime vendor = $rt.specVendor
Runtime spec version = $rt.specVersion
Runtime management spec version = $rt.managementSpecVersion
"""

def mem = ManagementFactory.memoryMXBean
def heapUsage = mem.heapMemoryUsage
def nonHeapUsage = mem.nonHeapMemoryUsage

println """MEMORY:
HEAP STORAGE:
Memory committed = $heapUsage.committed
Memory init = $heapUsage.init
Memory max = $heapUsage.max
Memory used = $heapUsage.used NON-HEAP STORAGE:
Non-heap memory committed = $nonHeapUsage.committed
Non-heap memory init = $nonHeapUsage.init
Non-heap memory max = $nonHeapUsage.max
Non-heap memory used = $nonHeapUsage.used
"""

println "GARBAGE COLLECTION:"
ManagementFactory.garbageCollectorMXBeans.each { gc ->
println " name = $gc.name"
println " collection count = $gc.collectionCount"
println " collection time = $gc.collectionTime"
String[] mpoolNames = gc.memoryPoolNames

mpoolNames.each {
mpoolName -> println " mpool name = $mpoolName"
}
}

Groovy JSON

JSON功能

功能
JsonSlurper JsonSlurper是一个将JSON文本或阅读器内容解析为Groovy数据的类结构,例如地图,列表和原始类型,如整数,双精度,布尔和字符串。
JsonOutput 此方法负责将Groovy对象序列化为JSON字符串。

反序列化

1
2
3
4
5
6
7
8
9
10
11
12
import groovy.json.JsonSlurper

def slurper = new JsonSlurper()
def obj = slurper.parseText('{"name":"John","id":"1"}')
println obj.name
println obj.id

def lst = slurper.parseText('{"List" : [2,3,4,5]}')
lst.each {
key, value ->
println("$key = $value")
}

序列化

1
2
3
4
5
6
7
8
9
10
import groovy.json.JsonOutput

def jsonOuter = new JsonOutput()
Map<String, Object> map = new HashMap<>()
map.put("name","hefuduo")
map.put("age",27)
map.put("ss",new Car(14))
String json = jsonOuter.toJson(map)

println json

Groovy DLS

Groovy允许在顶层语句的方法调用的参数周围省略括号。这被称为“命令链”功能。这个扩展的工作原理是允许一个人链接这种无括号的方法调用,在参数周围不需要括号,也不需要链接调用之间的点。

如果一个调用被执行为bcd,这将实际上等价于a(b).c(d)。

DSL或域特定语言旨在简化以Groovy编写的代码,使得它对于普通用户变得容易理解。以下示例显示了具有域特定语言的确切含义。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
class EmailDSL{
String toText
String fromText
String body

def static make(Closure closure){
EmailDSL dsl = new EmailDSL()
closure.delegate = dsl
closure.call()
}

def to(String toText){
this.toText = toText
}

def from(String fromText){
this.fromText = fromText
}

def body(String bodyText){
this.body = bodyText
}
def send(boolean send){
if (send){
println "From $fromText To $toText : $body"
}
}
}

EmailDSL.make {
to "James"
from "Leo"
body "How is everything going on"
send true
}
  • 使用接受闭包的静态方法。这是一个很麻烦的方式来实现DSL。
  • 在电子邮件示例中,类EmailDsl具有make方法。它创建一个实例,并将闭包中的所有调用委派给实例。这是一种机制,其中“to”和“from”节结束了EmailDsl类中的执行方法。
  • 一旦to()方法被调用,我们将文本存储在实例中以便以后格式化。
  • 我们现在可以使用易于为最终用户理解的简单语言调用EmailDSL方法。

Groovy 模板引擎

字符串中的简单模板

如果你采用下面的简单例子,我们首先定义一个名称变量来保存字符串“Groovy”。在println语句中,我们使用$符号来定义可以插入值的参数或模板。

1
2
def name = "Groovy" 
println "This Tutorial is about ${name}"

如果上面的代码在groovy中执行,将显示以下输出。输出清楚地显示$名称被由def语句分配的值替换

简单模板引擎

以下是SimpleTemplateEngine的示例,它允许您在模板中使用类似于JSP的scriptlet和EL表达式,以生成参数化文本。模板引擎允许绑定参数列表及其值,以便可以在具有定义的占位符的字符串中替换它们。

1
2
3
4
5
6
7
8
9
def text ='This Tutorial focuses on $TutorialName. In this tutorial you will learn 

about $Topic'

def binding = ["TutorialName":"Groovy", "Topic":"Templates"]
def engine = new groovy.text.SimpleTemplateEngine()
def template = engine.createTemplate(text).make(binding)

println template

如果上面的代码在groovy中执行,将显示以下输出。

现在让我们使用XML文件的模板功能。作为第一步,让我们将下面的代码添加到一个名为Student.template的文件中。在以下文件中,您将注意到,我们尚未添加元素的实际值,而是添加占位符。所以$ name,$ is和$ subject都被放置为占位符,需要在运行时替换。

1
2
3
4
5
6
<Student> 
<name>${name}</name>
<ID>${id}</ID>
<subject>${subject}</subject>
</Student>

现在,让我们添加我们的Groovy脚本代码来添加功能,可以使用实际值替换上面的模板。应该注意以下事项关于以下代码。

  • 占位符到实际值的映射通过绑定和SimpleTemplateEngine完成。绑定是一个映射,占位符作为键,替换值作为值。
1
2
3
4
5
6
7
8
9
10
11
import groovy.text.* 
import java.io.*

def file = new File("D:/Student.template")
def binding = ['name' : 'Joe', 'id' : 1, 'subject' : 'Physics']

def engine = new SimpleTemplateEngine()
def template = engine.createTemplate(file)
def writable = template.make(binding)

println writable

如果上面的代码在groovy中执行,将显示以下输出。从输出中可以看出,在相关占位符中成功替换了值。

1
2
3
4
5
<Student> 
<name>Joe</name>
<ID>1</ID>
<subject>Physics</subject>
</Student>

Groovy 元对象编程

元对象编程或MOP可以用于动态调用方法,并且可以即时创建类和方法。

那么这是什么意思呢?让我们考虑一个叫Student的类,它是一个没有成员变量或方法的空类。假设你必须在这个类上调用以下语句。

1
2
3
Def myStudent = new Student() 
myStudent.Name = ”Joe”;
myStudent.Display()

现在在元对象编程中,即使类没有成员变量Name或方法Display(),上面的代码仍然可以工作。

这如何工作?那么,为了这个工作,一个人必须实现GroovyInterceptable接口挂钩到Groovy的执行过程。以下是该接口的可用方法。

1
2
3
4
5
6
7
Public interface GroovyInterceptable { 
Public object invokeMethod(String methodName, Object args)
Public object getproperty(String propertyName)
Public object setProperty(String propertyName, Object newValue)
Public MetaClass getMetaClass()
Public void setMetaClass(MetaClass metaClass)
}

所以在上面的接口描述中,假设你必须实现invokeMethod(),它会被调用的每个方法,要么存在或不存在。

缺失属性

所以,让我们看一个例子,我们如何为缺失的属性实现元对象编程。以下键应该注意以下代码。

  • 类Student没有定义名为Name或ID的成员变量。
  • 类Student实现GroovyInterceptable接口。
  • 有一个称为dynamicProps的参数,将用于保存即时创建的成员变量的值。
  • 方法getproperty和setproperty已被实现以在运行时获取和设置类的属性的值。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Example {
static void main(String[] args) {
Student mst = new Student();
mst.Name = "Joe";
mst.ID = 1;

println(mst.Name);
println(mst.ID);
}
}

class Student implements GroovyInterceptable {
protected dynamicProps=[:]

void setProperty(String pName,val) {
dynamicProps[pName] = val
}

def getProperty(String pName) {
dynamicProps[pName]
}
}

以下代码的输出将是 -

1
2
Joe 
1

缺失方法

所以,让我们看一个例子,我们如何为缺失的属性实现元对象编程。以下键应该注意下面的代码 -

  • 类学生现在实现invokeMethod方法,它被调用,而不管该方法是否存在。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class Example {
static void main(String[] args) {
Student mst = new Student();
mst.Name = "Joe";
mst.ID = 1;

println(mst.Name);
println(mst.ID);
mst.AddMarks();
}
}

class Student implements GroovyInterceptable {
protected dynamicProps = [:]

void setProperty(String pName, val) {
dynamicProps[pName] = val
}

def getProperty(String pName) {
dynamicProps[pName]
}

def invokeMethod(String name, Object args) {
return "called invokeMethod $name $args"
}
}

以下代码的输出如下所示。请注意,即使方法Display不存在,也没有缺少方法异常的错误。

1
2
Joe 
1

Java泛型拓展

基础知识之协变与逆变

1
2
3
4
5
6
7
8
// public final class Integer extends Number  
Number num = new Integer(1);
List<Number> list = new ArrayList<>();
list.add(new Integer(3));
ArrayList<Number> list = new ArrayList<Integer>(); //type mismatch 这两个根本就不是一个类型

List<? extends Number> list = new ArrayList<Number>();
list.add(new Integer(1)); //error

为什么Number的对象可以由Integer实例化,而ArrayList<Number>的对象却不能由ArrayList<Integer>实例化?List中的<? extends Number>声明其元素是NumberNumber的派生类,为什么不能add Integer?为了解决这些问题,需要了解Java中的逆变和协变以及泛型中通配符用法。

​ Java中String类型是继承自Object的,姑且记做String ≦ Object,表示String是Object的子类型,String的对象可以赋给Object的对象。而Object的数组类型Object[],理解成是由Object构造出来的一种新的类型,可以认为是一种构造类型,记f(Object),那么可以这么来描述协变和逆变:

当A ≦ B时,如果有f(A) ≦ f(B),那么f叫做协变;

当A ≦ B时,如果有f(B) ≦ f(A),那么f叫做逆变;

如果上面两种关系都不成立则叫做不可变。

JAVA中泛型是不变的,可有时需要实现逆变与协变,怎么办呢?这时就需要通配符?

Read more »
0%