Groovy 语法总结

 

Groovysh 相关配置

Variables

Shell 变量是无类型的,即不是 def 类型也不是任何其它类型

下面定义了一个 Shell 变量

foo = "bar"

但是下面一行定义了一个本地变量,并且不会保存到 shell 环境中

def foo = "bar"

可以通过设置 :set interpreterMode 来定义有类型的变量(def 或其它类型)

字符串插值(String interpolation)

除了单引号和三重单引号的字符串外,任何 Groovy 表达式都可以插入到字符串中。插值用表达式得到的值替换字符串中的占位符。占位符表达式由 ${} 包围。对于明确的点分表达式(dotted expression),可以省略花括号,即只使用 $ 前缀。

def name = 'Guillaume' // a plain string
def greeting = "Hello ${name}"

assert greeting.toString() == 'Hello Guillaume'

${} 占位符中不但允许使用表达式(expression),还可以使用语句(statements)。但是,一条语句的值是 null 。因此,如果在该占位符中插入了多个语句,则最后一个应以某种方式返回要插入的有意义的值。例如,”1 + 2的和等于 ${def a = 1; def b = 2; a + b}“,但是最好在 GString 占位符中只使用简单的表达式。

除了 ${} 占位符,我们还可以在点分表达式(dotted expression)前使用单独的 $ 符号:

def person = [name: 'Guillaume', age: 36]
assert "$person.name is $person.age years old" == 'Guillaume is 36 years old'

但是只有 a.ba.b.c 等形式的点分表达式有效。带括号的表达式,如方法调用中的圆括号,闭包的大括号(不属于属性表达式或算术运算符的点)是无效的。给定以下数字变量定义:

def number = 3.14

以下语句将引发 groovy.lang.MissingPropertyException,因为 Groovy 认为你正在尝试访问 number 对象的 toString 属性,而不是 toString() 方法:

shouldFail(MissingPropertyException) {
    println "$number.toString()"
}

你可以认为 "$number.toString()" 会被解释为 "${number.toString}()"

Optionality

  • 在不产生歧义并且至少有一个参数的情况下,方法调用可以省略括号 ()
println 'Hello World'
def maximum = Math.max 5, 10

如果方法没有参数或会产生歧义,则必须带括号

println()
println(Math.max(5, 10))
  • 如果一行只有一个语句,可以省略行尾的分号 ;,多个语句在一行则需要使用分号来分隔
boolean a = true; assert a
  • 方法或闭包的最后一个表达式的计算结果会作为返回值返回,因此关键字 return 可以省略

  • Groovy 中类和方法默认是 public,因此下面两段定义一样

public class Server {
    public String toString() { "a server" }
}
class Server {
    String toString() { "a server" }
}

Groovy中的真值(The Groovy Truth)

// 布尔值 true
assert true
assert !false

// 非空集合
assert [1, 2, 3]
assert ![]

// 至少一个匹配
assert ('a' =~ /a/)
assert !('a' =~ /b/)

// iterator 和 enumeration 可以读取下一个元素
assert [0].iterator()
assert ![].iterator()
Vector v = [0] as Vector
Enumeration enumeration = v.elements()
assert enumeration
enumeration.nextElement()
assert !enumeration

// 非空映射
assert ['one' : 1]
assert ![:]

// 非空字符串
assert 'a'
assert !''

// 非零值
assert 1
assert -1
assert !0

//非空引用
assert new Object()
assert !null

闭包(Closure)

闭包定义

{ [closureParameters -> ] statements }

如果函数最后一个参数是闭包,可以将闭包放在参数列表外面,附在函数调用之后

// 定义一个有两个参数的函数,最后一个参数是闭包
def f (n, cl) {
   cl(n)
}

// 定义一个闭包
def isEven = {it % 2 == 0}

// 以下函数调用等价
f(6, isEven)

// 参数可以不带括号
f 6, isEven

// 传入一个匿名闭包
f(6, {it % 2 != 0})

// 最后一个参数是闭包的话,可以写在参数括号外面
f(6) {
    it % 2 != 0
}

// 不带参数括号,传入匿名闭包,注意参数间的逗号
f 6, {
    it % 2 == 0
}

读取文件

new File(baseDir, 'haiku.txt').eachLine { line ->
    println line
}

eachLine 方法按行遍历对象,其闭包参数可以接受一或两个参数,第2个参数表示行数。

new File(baseDir, 'haiku.txt').eachLine { line, nb ->
    println "Line $nb: $line"
}

将文本中的每一行放入一个列表

def list = new File(baseDir, 'haiku.txt').collect {it}
// 或
def list = new File(baseDir, 'haiku.txt').readLines()

执行外部命令(Executing External Processes)

将要执行的外部命令作为字符串,然后调用 execute 方法,该方法返回一个 java.lang.Process 对象,允许处理 in/out/err 字节流以及 Exit Code

def process = "ls -l".execute()
println "Found text ${process.text}"

将多行的字符串读入列表

String str = 'ls -l'.execute().text
List list = str.readLines()

使用集合(Working with Collections)

列表

[] 操作符用来创建列表

def list = [5, 6, 7, 8]
assert list.get(2) == 7
assert list[2] == 7
assert list instanceof java.util.List

def emptyList = []
assert emptyList.size() == 0
emptyList.add(5)
assert emptyList.size() == 1

遍历列表(Iterating on a List)

使用 eacheachWithIndex 方法遍历列表,它们接受一个闭包作为参数,并在每个列表元素上执行闭包代码

[1, 2, 3].each {
    println "Item: $it" // `it` is an implicit parameter corresponding to the current element
}
['a', 'b', 'c'].eachWithIndex { it, i -> // `it` is the current element, while `i` is the index
    println "$i: $it"
}

类似于 python 的 map 方法,集合可以使用 collect 方法对集合元素进行转换,它接收一个闭包作为参数,并在所有元素上执行该闭包,返回一个转换后的新列表。如果同时传入一个列表和闭包作为参数,则转换后的元素会追加到该列表。

assert [1, 2, 3].collect { it * 2 } == [2, 4, 6]

// shortcut syntax instead of collect
assert [1, 2, 3]*.multiply(2) == [1, 2, 3].collect { it.multiply(2) }

def list = [0]
// it is possible to give `collect` the list which collects the elements
assert [1, 2, 3].collect(list) { it * 2 } == [0, 2, 4, 6]
assert list == [0, 2, 4, 6]

inject(Object initialValue, Closure closure) 遍历集合,将初始值和集合第一个元素作为参数传递给闭包,闭包的返回值和集合的第二个元素再次作为参数传递给闭包,一直重复直到集合中元素遍历完成。

assert 'The quick brown fox' ==
    ['quick', 'brown', 'fox'].inject('The') { acc, val -> acc << " ${val}" }

def max = { a, b -> [a, b].max() }  // define a closure
def animals = ['bat', 'rat', 'cat']
assert 'rat' == animals.inject('aaa', max)

上例的直观解释

   initVal  animals[0]
      v        v
max('aaa',   'bat')  =>  'bat'  animals[1]
                           v       v
                     max('bat',  'rat')  =>  'rat'  animals[2]
                                               v       v
                                         max('rat',  'cat')  =>  'rat'

inject(Closure closure) 方法使用集合中第一个元素作为初始值。

列表操作(Manipulating Lists)

assert [1, 2, 3].find { it > 1 } == 2           // find 1st element matching criteria
assert [1, 2, 3].findAll { it > 1 } == [2, 3]   // find all elements matching critieria
assert [1, 2, 3].every { it < 5 }               // returns true if all elements match the predicate
assert [1, 2, 3].any { it > 2 }                 // returns true if any element matches the predicate
assert [1, 2, 3, 4, 5, 6].sum() == 21           // sum anything with a plus() method
assert [1, 2, 3].sum(1000) == 1006
assert [1, 2, 3].join('-') == '1-2-3'           // String joining
assert [1, 2, 3].inject(0) {
    count, item -> count + item                 // reduce operation
} == 6

集合操作(Set operations)

assert 'a' in ['a','b','c']                         // returns true if an element belongs to the list
assert ['a','b','c'].contains('a')                  // equivalent to the `contains` method in Java
assert [1,3,4].containsAll([1,4])                   // `containsAll` will check that all elements are found

assert [1,2,3,3,3,3,4,5].count(3) == 4              // count the number of elements which have some value
assert [1,2,3,3,3,3,4,5].count { it%2==0 } == 2     // count the number of elements which match the predicate

assert [1,2,4,6,8,10,12].intersect([1,3,6,9,12]) == [1,6,12]

assert [1,2,3].disjoint( [4,6,9] )
assert ![1,2,3].disjoint( [2,4,6] )

添加删除列表元素(Adding or removing elements)

使用 [] 创建空列表,<< 向列表追加元素

def list = []
list << 5
assert list.size() == 1

list << 7 << 'i' << 11
assert list == [5, 7, 'i', 11]

list << ['m', 'o']
assert list == [5, 7, 'i', 11, ['m', 'o']]

//first item in chain of << is target list
assert ([1, 2] << 3 << [4, 5] << 6) == [1, 2, 3, [4, 5], 6]

//using leftShift is equivalent to using <<
assert ([1, 2, 3] << 4) == ([1, 2, 3].leftShift(4))

assert [1, 2] + 3 + [4, 5] + 6 == [1, 2, 3, 4, 5, 6]
// equivalent to calling the `plus` method
assert [1, 2].plus(3).plus([4, 5]).plus(6) == [1, 2, 3, 4, 5, 6]

注意: 与 << 相比,每次调用 + 操作符都会创建一个新列表,可能导致性能问题。

使用 remove 方法来删除元素,可以传入元素索引,也可以传入元素值。如果列表元素是整数值或任何其它类型,可以使用对应的 removeAtremoveElement 方法

def list = ['a','b','c','d','b']
assert list.remove(2) == 'c'        // remove the third element, and return it
assert list == ['a','b','d','b']
assert list.remove('b')             // remove first 'b', and return true because element removed
assert ! list.remove('z')           // return false because no elements removed
assert list == ['a','d','b']

def list = [1,2,3,4,5,4]
assert list.removeAt(1) == 2        // remove element at index 1, and return it
assert list == [1,3,4,5,4]
assert list.removeElement(4)        // remove first 5 and return true
assert list == [1,3,5,4]

使用 clear 方法清空列表

def list= ['a',2,'c',4]
list.clear()
assert list == []

映射(Maps)

[:] 操作符用来创建映射

def emptyMap = [:]
assert emptyMap.size() == 0
emptyMap.put("foo", 5)

def map = [name: 'Gromit', likes: 'cheese', id: 1234]
assert map.name == 'Gromit'
assert map.get('likes') == 'cheese'i
assert map['id'] == 1234
assert map instanceof java.util.Map

注意: 映射的键默认是 String 类型,也就是说 [a:1]['a':1] 一样,如果有个变量叫 a,我们又想用该变量的值作为映射的键,需要使用括号 () 来转义。

def a = 'Bob'
ages = [(a): 43]           // now we escape `a` by using parenthesis
assert ages['Bob'] == 43   // and the value is found!

遍历映射(Iterating on maps)

def map = [
        Bob  : 42,
        Alice: 54,
        Max  : 33
]

// `entry` is a map entry
map.each { entry ->
    println "Name: $entry.key Age: $entry.value"
}

// `entry` is a map entry, `i` the index in the map
map.eachWithIndex { entry, i ->
    println "$i - Name: $entry.key Age: $entry.value"
}

// Alternatively you can use key and value directly
map.each { key, value ->
    println "Name: $key Age: $value"
}

// Key, value and i as the index in the map
map.eachWithIndex { key, value, i ->
    println "$i - Name: $key Age: $value"
}

添加删除映射元素(Adding or removing elements)

使用 put 方法添加键值对,下标操作符 []putAll 方法添加或修改元素

def defaults = [1: 'a', 2: 'b']
def overrides = [2: 'z', 5: 'x', 13: 'x']

def result = new LinkedHashMap(defaults)
result.put(15, 't')
result[17] = 'u'
result.putAll(overrides)
assert result == [1: 'a', 2: 'z', 5: 'x', 13: 'x', 15: 't', 17: 'u']

使用 clear 方法清空映射

def m = [1:'a', 2:'b']
m.clear()
assert m == [:]

使用 groupBy 方法对映射进行分类

assert ['a', 7, 'b', [2, 3]].groupBy {
    it.class
} == [(String)   : ['a', 'b'],
      (Integer)  : [7],
      (ArrayList): [[2, 3]]
]

assert [
        [name: 'Clark', city: 'London'], [name: 'Sharma', city: 'London'],
        [name: 'Maradona', city: 'LA'], [name: 'Zhang', city: 'HK'],
        [name: 'Ali', city: 'HK'], [name: 'Liu', city: 'HK'],
].groupBy { it.city } == [
        London: [[name: 'Clark', city: 'London'],
                 [name: 'Sharma', city: 'London']],
        LA    : [[name: 'Maradona', city: 'LA']],
        HK    : [[name: 'Zhang', city: 'HK'],
                 [name: 'Ali', city: 'HK'],
                 [name: 'Liu', city: 'HK']],
]

范围(Range)

  • .. (inclusive, 闭区间)
  • ..< (half-open, 左闭右开区间)
// an inclusive range
def range = 5..8
assert range.size() == 4
assert range.get(2) == 7
assert range[2] == 7
assert range instanceof java.util.List
assert range.contains(8)

// lets use a half-open range
range = 5..<8
assert range.size() == 3
assert range.get(2) == 7
assert range[2] == 7
assert range instanceof java.util.List
assert !range.contains(8)

//get the end points of the range without using indexes
range = 1..10
assert range.from == 1
assert range.to == 10

范围可用于所有实现了 java.lang.Comparable 接口的 Java 对象,比如 String 对象

// an inclusive range
def range = 'a'..'d'
assert range.size() == 4
assert range.contains('a')
assert range.contains('d')

切片(Slicing)

可以使用 subscript expressionList, Array, MapString 对象进行索引。

def text = 'nice cheese gromit!'
def x = text[2]

assert x == 'c'
assert x.class == String

def sub = text[5..10]
assert sub == 'cheese'