目录
本文主要介绍 Groovy 语言的操作符,基于 Groovy 2.5.5 版本。
1. 算术操作符
Groovy 支持你所熟知的来自数学或其他编程语言中的常用算术运算符。Groovy 支持 Java 中的所有算术运算符。我们将在下面的一系列示例中学习它们。
1.1 普通算术操作符
Groovy 支持的二元算术操作符如下表所示:
操作符 | 用途 | 说明 |
---|---|---|
| 相加 | |
| 相减 | |
| 相乘 | |
| 相除 | 如果想使用整数除法,请使用 intdiv() 方法,请同时查看 integer division 章节了解更多关于整数除法的返回值类型信息。 |
| 取余 | |
| 乘方 | 请查看 the power operation 章节来了解更多关于乘方操作符的返回值信息。 |
下面是关于这些操作符用法的一些例子:
assert 1 + 2 == 3
assert 4 - 3 == 1
assert 3 * 5 == 15
assert 3 / 2 == 1.5
assert 10 % 3 == 1
assert 2 ** 3 == 8
1.2 一元操作符
+ 和 - 操作符也可以被当作一元操作符来使用:
assert +3 == 3
assert -4 == 0 - 4
assert -(-1) == 1 //1
//1:将一元操作符 - 应用到表达式上时,需要在表达式上添加括号
在一元算术操作符方面,Groovy 也同时支持前缀和后缀形式的自增(++)和自减(--)操作符:
def a = 2
def b = a++ * 3 //1
assert a == 3 && b == 6
def c = 3
def d = c-- * 2 //2
assert c == 2 && d == 6
def e = 1
def f = ++e + 3 //3
assert e == 2 && f == 5
def g = 4
def h = --g + 1 //4
assert g == 3 && h == 4
//1: 后缀自增操作符会在表达式求值完成后,对 a 执行自增操作,并将表达式的值赋值给 b
//2: 后缀自减操作符会在表达式求值完成后,对 c 执行自减操作,并将表达式的值赋值给 d
//3: 前缀自增操作符会先对 e 执行自增操作,然后对表达式进行求值并赋值给 f
//4: 前缀自减操作符会先对 g 执行自减操作,然后对表达式进行求值并赋值给 h
1.3 赋值算术操作符
前面我们看到的各种二元算术操作符都具有对应的赋值算术操作符形式:
-
+=
-
-=
-
*=
-
/=
-
%=
-
**=
我们来看些实例:
def a = 4
a += 3
assert a == 7
def b = 5
b -= 3
assert b == 2
def c = 5
c *= 3
assert c == 15
def d = 10
d /= 2
assert d == 5
def e = 10
e %= 3
assert e == 1
def f = 3
f **= 2
assert f == 9
2. 关系运算符
关系操作符允许在对象之间执行比较运算,以确定两个对象是否相同,或者是否一个比另一个大、小或等于另一个。Groovy 中有如下的关系运算符:
操作符 | 用途 |
---|---|
| 等于 |
| 不等于 |
| 小于 |
| 小于等于 |
| 大于 |
| 大于等于 |
下面是使用这些操作符进行简单的算术比较的例子:
assert 1 + 2 == 3
assert 3 != 4
assert -2 < 3
assert 2 <= 2
assert 3 <= 4
assert 5 > 1
assert 5 >= -2
3. 逻辑运算符
Groovy 为布尔表达式提供了 3 个逻辑运算符:
-
&&
: 逻辑与 -
||
: 逻辑或 -
!
: 逻辑非
用下面例子作简单演示:
assert !false
assert true && true
assert true || false
3.1 优先级
逻辑非 !运算符的优先级高于逻辑与 && 运算符:
assert (!false && false) == false //1
//1: 这里断言为真(因为括号内的表达式为假),因为逻辑非运算符的优先级高于逻辑与运算符,所有逻辑非操作符只作用于第一个 false,不然的话,它就得作用于逻辑与运算的结果,导致断言失败。
逻辑与 && 运算符的优先级高于逻辑或 || 运算符:
assert true || true && false //1
//1: 这里断言为真,因为 && 运算符的优先级比 || 运算符高,因此 || 运算最后执行,并返回真值。
3.2 短路特性
逻辑或 || 运算符具有短路特性:如果运算符的左操作数为真,那么它就已经知道结果无论如何都是真,因此右操作数就不会执行计算。仅当左操作数为假时,右操作数才会执行计算。
类似地,逻辑与操作也具有短路特性:如果运算符的左操作数为假,那么它就已经知道结果无论如何都是假,因此右操作数就不会执行计算。仅当左操作数为真时,右操作数才会执行计算。
boolean checkIfCalled() { //1
called = true
}
called = false
true || checkIfCalled()
assert !called //2
called = false
false || checkIfCalled()
assert called //3
called = false
false && checkIfCalled()
assert !called //4
called = false
true && checkIfCalled()
assert called //5
//1: 创建一个叫做 checkIfCalled 的函数,它内部会将 called 变量设置为 true
//2: 将 called 标记置为 false 后,我们验证下 || 操作符的左操作数为真时,不会对右操作数执行计算,因为 || 操作符具有短路特性
//3: 由于 || 操作符的左操作数为假,因此会对又操作数执行计算,导致 called 标记变为 true
//4: 类似的,我们验证 && 操作符的左操作数为假时,不会对右操作数执行计算
//5: 但是当 && 操作符的左操作数为真时,会对右操作数执行计算
4. 位运算操作符
Groovy 中有 4 个位运算操作符:
-
&
: 位与 -
|
: 位或 -
^
: 位异或 -
~
: 按位取反
位运算操作符可以应用于 byte 或 int 型数据,并返回一个 int 值:
int a = 0b00101010
assert a == 42
int b = 0b00001000
assert b == 8
assert (a & a) == a // 位与
assert (a & b) == b // 位与并返回同时为 1 的比特
assert (a | a) == a // 位或
assert (a | b) == a // 位或并返回所有为 1 的比特
int mask = 0b11111111 // 设置一个只检查最后 8 位的掩码
assert ((a ^ a) & mask) == 0b00000000 // 对自身进行按位异或将返回 0
assert ((a ^ b) & mask) == 0b00100010 // 位异或
assert ((~a) & mask) == 0b11010101 // 按位取反
值得注意的是,原始类型的内部表示遵循 Java 语言规范。特别地,原始类型是有符号的,这意味着对于按位取反操作,通常比较好的实践是:使用掩码来提取那些必需的比特。
在 Groovy 中,位操作符是可以重载的,意味着你可以针对任意类型的对象定义这些操作符作用在其上的行为。
5. 条件运算符
5.1 非运算符
非运算符由感叹号 !表示,它会反转底层布尔表达式的计算结果。特别是非运算符可以和 Groovy 真值(Groovy truth)结合使用:
assert (!true) == false // 非 true 即 false
assert (!'foo') == false // 'foo' 是非空字符串,求值后为 true, 取非后得到 false
assert (!'') == true // '' 是空字符串,求值后为 false, 取非后得到 true
5.2 三元运算符
三元操作符是一个缩写的表达式,它等效于一个由 if/else 分支语句组织起来的赋值语句。
除了使用下面的这个 if/else 语句外:
if (string!=null && string.length()>0) {
result = 'Found'
} else {
result = 'Not found'
}
你可以把它简写成:
result = (string!=null && string.length()>0) ? 'Found' : 'Not found'
三元操作符也是可以和 Groovy 真值(Groovy truth)结合使用的,所有上面的写法可以进一步简化:
result = string ? 'Found' : 'Not found'
5.3 埃尔维斯操作符
埃尔维斯操作符是三元运算符的缩写形式。使用这种便捷写法的一个实际场景是:如果一个表达式求值为假(基于 Groovy 真值)时需要返回一个合理的默认值的情况。下面是个简单的例子:
displayName = user.name ? user.name : 'Anonymous' //1
displayName = user.name ?: 'Anonymous' //2
//1: 使用三元操作符时,你必须重复你想要赋值的那个值
//2: 使用埃尔维斯操作符时,如果被测试值为真时,就会使用该值作为返回值
使用埃尔维斯操作符能够降低代码的复杂性也能够减小代码重构时发生错误的几率:因为不需要在条件和真值返回值部分重复被测试的表达式。
6. 对象操作符
6.1 安全导航操作符
安全导航操作符 ?. 主要用来避免空指针异常(NullPointerException)。通常,当你有一个指向某个对象的引用时,在使用它进行方法或属性访问前,都需要检查它是否为 null。为了避免这种检查,安全导航操作符在引用为 null 时,会直接返回 null,而不是抛出空指针异常。如下例所示:
def person = Person.find { it.id == 123 } // find 将会返回 null
def name = person?.name // 使用安全导航操作符可以避免空指针异常
assert name == null // 结果为 null
6.2 直接字段访问操作符
在 Groovy 中,当你写了一段类似下面的代码时:
class User {
public final String name // 公有字段 name
User(String name) { this.name = name}
String getName() { "Name: $name" } // name 的读取器(getter),会返回一个定制化的字符串
}
def user = new User('Bob')
assert user.name == 'Name: Bob' // 此处会调用读取器
user.name 调用会触发一个到同名的属性的调用,也就是说,在这里会触发对 name 的读取器(getName)的调用。如果你的确是想获取字段 name 的值,而不是调用它的读取器,你就可以向下面这样使用直接属性访问操作符 .@ 来实现:
assert user.@name == 'Bob' //1
//1: 使用 .@ 操作符强制访问字段自身,而不是对应的获取器
6.3 方法指针操作符
方法指针操作符(.&)被用来获取方法的引用,并存储到一个变量中,以便后续使用:
def str = 'example of method reference' // 变量 str 中存储了一个字符串
def fun = str.&toUpperCase // 把 str 实例上的 toUpperCase 方法的引用存储到变量 fun 中
def upper = fun() // 可以向普通的方法调用一样调用 fun
assert upper == str.toUpperCase() // 结果和在 str 上直接调用 toUpperCase 方法是一样的
使用方法指针具有许多优点。首先,方法指针的类型是 groovy.lang.Closure,所以在任何可以使用闭包的地方都可以使用方法指针。特别地,它适合用来转换一个已有的方法以满足策略模式的需求:
def transform(List elements, Closure action) { //1
def result = []
elements.each {
result << action(it)
}
result
}
String describe(Person p) { //2
"$p.name is $p.age"
}
def action = this.&describe //3
def list = [
new Person(name: 'Bob', age: 42),
new Person(name: 'Julia', age: 35)] //4
assert transform(list, action) == ['Bob is 42', 'Julia is 35'] //5
//1: transform 方法会对参数列表中的每一个元素调用 action 闭包,并返回一个新列表
//2: 定义一个接受 Person 类型参数,返回字符串的函数
//3: 创建一个指向 describe 函数的方法指针
//4: 创建一个参数列表
//5: 可以在需要闭包的地方使用方法指针
方法指针是绑定在方法接受者和方法名上的。方法参数是在运行时解析的,这就是说,如果你有多个相同名称的方法,语法也是一样的,并没有什么不同,只是解析具体要调用的方法是在运行时完成的:
def doSomething(String str) { str.toUpperCase() } //1
def doSomething(Integer x) { 2*x } //2
def reference = this.&doSomething //3
assert reference('foo') == 'FOO' //4
assert reference(123) == 246 //5
//1: 定义一个重载的 doSomething 方法,接受字符串类型参数
//2: 定义一个重载的 doSomething 方法,接受整形参数
//3: 创建一个指向 doSomething 的方法指针,并没有指定参数类型
//4: 使用字符串参数调用方法指针时,会调用字符串版本的 doSomething 方法
//5: 使用整形参数调用方法指针时,会调用整形版本的 doSomething 方法
7. 正则表达式操作符
7.1 模式操作符
模式操作符(~)提供了一个创建 java.util.regex.Pattern 实例的简单方式:
def p = ~/foo/
assert p instanceof Pattern
尽管你通常看到模式操作符一般和斜线风格的字符串一起使用,但其实它可以和任何形式的 Groovy 字符串一起使用:
p = ~'foo' //1
p = ~"foo" //2
p = ~$/dollar/slashy $ string/$ //3
p = ~"${pattern}" //4
//1: 使用单引号风格的字符串构建模式
//2: 使用双引号风格的字符串构建模式
//3: 使用美元斜线风格的字符中构建模式,不用在字符串内对 $ 和 / 字符进行转意
//4: 也可以使用 GString 创建模式
7.2 查找操作符
除了先构建一个模式外,也可以直接使用查找操作符(=~)来构建一个 java.util.regex.Matcher 对象:
def text = "some text to match"
def m = text =~ /match/ //1
assert m instanceof Matcher //2
if (!m) { //3
throw new RuntimeException("Oops, text not found!")
}
//1: 查找操作符 =~ 使用右侧的模式对左侧的 text 变量创建了一个 Matcher
//2: 查找操作符的返回类型是 Matcher
//3: 等效于调用 if(!m.find())
因为 Matcher 对象强制转换成 boolean 值是通过调用它的 find 方法实现的,所以查找操作符 =~ 用作判断式时(在 if, while 语句中等)表现出的行为和 Perl 语言中的 =~ 操作符一致。
7.3 匹配操作符
匹配操作符(==~)看起来像查找操作符(=~)的一个变种,它不返回 Matcher 对象,而是返回一个 boolean 值,并且要求输入值与模式严格匹配:
m = text ==~ /match/ //1
assert m instanceof Boolean //2
if (m) { //3
throw new RuntimeException("Should not reach that point!")
}
//1: 匹配操作符会使用右侧的模式来严格匹配左侧的变量
//2: 匹配操作符的返回类型是布尔值
//3: 等价与调用 if(text ==~ /match/)
8. 其他操作符
8.1 展开操作符
展开点操作符(*.),简称为展开操作符,通常被用来在聚合对象的每一个元素上执行操作。它等效于在聚合对象的每个元素上调用操作,然后把所有结果收集到一个列表里:
class Car {
String make
String model
}
def cars = [
new Car(make: 'Peugeot', model: '508'),
new Car(make: 'Renault', model: 'Clio')] //1
def makes = cars*.make //2
assert makes == ['Peugeot', 'Renault'] //3
//1: 创建一个有 Car 类型元素组成的列表。列表是一个聚合对象。
//2: 在列表上调用展开操作符,访问每个元素的 make 属性
//3: 返回一个包含各个元素对应的 make 属性所组成的列表
表达式 car*.make 等价于 car.collect { it.make }。当所访问的属性不是被操作列表自身的属性时,Groovy 的 GPath 语法允许使用展开点操作符的缩写形式,但是操作仍然会自动展开到列表的每一个元素上。在前面的例子中,我们就可以使用 car.make 这个简写形式,但是通常还是推荐显式地写出展开点操作符。
展开点操作符是 null 安全的,这意味着,如果被操作集合中有元素为 null 时,将会返回 null,而不是抛出 NullPointerException 异常。
cars = [
new Car(make: 'Peugeot', model: '508'),
null, //1
new Car(make: 'Renault', model: 'Clio')]
assert cars*.make == ['Peugeot', null, 'Renault'] //2
assert null*.make == null //3
//1: 创建一个包含 null 元素的列表
//2: 使用展开操作符不会抛出空指针异常
//3: 展开操作符的接受者也可能为 null,此时返回值也是 null
展开操作符可以被用于任何实现了 Iterable 接口的类上:
class Component {
Long id
String name
}
class CompositeObject implements Iterable<Component> {
def components = [
new Component(id: 1, name: 'Foo'),
new Component(id: 2, name: 'Bar')]
@Override
Iterator<Component> iterator() {
components.iterator()
}
}
def composite = new CompositeObject()
assert composite*.id == [1,2]
assert composite*.name == ['Foo','Bar']
当操作嵌套的聚合数据结构时,可以使用多个级联的展开点操作符,如下例中的 cars*.models.*name :
class Make {
String name
List<Model> models
}
@Canonical
class Model {
String name
}
def cars = [
new Make(name: 'Peugeot',
models: [new Model('408'), new Model('508')]),
new Make(name: 'Renault',
models: [new Model('Clio'), new Model('Captur')])
]
def makes = cars*.name
assert makes == ['Peugeot', 'Renault']
def models = cars*.models*.name
assert models == [['408', '508'], ['Clio', 'Captur']]
assert models.sum() == ['408', '508', 'Clio', 'Captur'] // 展平一层
assert models.flatten() == ['408', '508', 'Clio', 'Captur'] // 展平所有层(此处仅有一层)
对于嵌套集合类的情况,可以考虑使用 collectNested DGM 方法,而不是展开点操作符:
class Car {
String make
String model
}
def cars = [
[
new Car(make: 'Peugeot', model: '408'),
new Car(make: 'Peugeot', model: '508')
], [
new Car(make: 'Renault', model: 'Clio'),
new Car(make: 'Renault', model: 'Captur')
]
]
def models = cars.collectNested{ it.model }
assert models == [['408', '508'], ['Clio', 'Captur']]
8.1.1 展开方法参数
有时可能你要调用的那个方法的参数已经存在于某个列表中,你必须做些适配把它们转化为方法的参数。在这样情况下,你可以使用展开操作符来调用该方法。举个例子,假如你有如下的方法签名:
int function(int x, int y, int z) {
x*y+z
}
假设你还有下面这个列表:
def args = [4,5,6]
那么你可以向下面这样调用该方法,而不用定义任何中间变量:
assert function(*args) == 26
我们甚至允许混合正常参数和展开参数:
args = [4]
assert function(*args,5,6) == 26
8.1.2 展开列表元素
当在列表字面量中使用展开操作符时,效果看起来就像被展开的列表元素被直接内联到了被操作的列表字面量中:
def items = [4,5] //1
def list = [1,2,3,*items,6] //2
assert list == [1,2,3,4,5,6] //3
//1: 定义一个列表 items
//2: 我们想把 items 列表中的元素直接插入到 list 列表中,而不调用 addAll 方法
//3: items 列表的内容被内联到了 list 列表中
8.1.3 展开映射元素
展开映射操作符和展开列表操作符类似,只是它操作的是映射。它允许你将一个映射的元素内联到另一个映射字面量中,如下例所示:
def m1 = [c:3, d:4] //1
def map = [a:1, b:2, *:m1] //2
assert map == [a:1, b:2, c:3, d:4] //3
//1: m1 是我们想要内联的映射
//2: 我们使用 *:m1 的语法来将 m1 的内容展开到 map 映射中
//3: 现在 map 包含 m1 中的所有元素
展开映射操作符的使用位置是会对最终结果产生影响的,如下面例子所示:
def m1 = [c:3, d:4] //1
def map = [a:1, b:2, *:m1, d: 8] //2
assert map == [a:1, b:2, c:3, d:8] //3
//1: m1 是我们想要内联的映射
//2: 我们使用 *:m1 的语法来将 m1 的内容展开到 map 映射中,但是在展开操作后,我们重新定义了键 d 的值
//3: 现在 map 包含 m1 中的所有的键,但是键 d 对应的值是修改后的
8.2 区间操作符
Groovy 支持区间的概念,并且提供了区间操作符(..)来创建区间对象:
def range = 0..5 //1
assert (0..5).collect() == [0, 1, 2, 3, 4, 5] //2
assert (0..<5).collect() == [0, 1, 2, 3, 4] //3
assert (0..5) instanceof List //4
assert (0..5).size() == 6 //5
//1: 一个由整数组成的区间
//2: 一个由整数组成的闭区间(包含首尾元素)
//3: 一个有整数组成的左闭右开区间(包含首元素,不包含尾元素)
//4: groovy.lang.Range 实现了 List 接口
//5: 可以在区间上调用 size() 方法
区间的实现是很轻量的,因为只有起始和结尾元素会被存储下来。你可以从任意具有 next() 和 previous() 方法且实现了 Comparable 接口的对象来创建区间。next 和 previous 方法分别用来确定区间里的后一个和前一个元素。例如你可以向下面这样创建一个字符组成的区间:
assert ('a'..'d').collect() == ['a','b','c','d']
8.3 比较操作符
比较操作符(Spaceship operator)内部其实是委派给 compareTo 方法的:
assert (1 <=> 1) == 0
assert (1 <=> 2) == -1
assert (2 <=> 1) == 1
assert ('a' <=> 'z') == -1
8.4 下标操作符
下标操作符 [] 是 getAt 或 putAt 方法的速写,具体代表的含有主要要看操作符是位于赋值运算符的左侧还是右侧:
def list = [0,1,2,3,4]
assert list[2] == 2 //1
list[2] = 4 //2
assert list[0..2] == [0,1,4] //3
list[0..2] = [6,6,6] //4
assert list == [6,6,6,3,4] //5
//1: [2] 可以使用 .getAt(2) 替换
//2: 如果位于赋值操作符的左侧,实际相当于调用 putAt
//3: getAt 也支持 Range 类型的参数
//4: 同样 putAt 也支持 Range 类型的参数
//5: 列表被改变了
使用下标操作符,结合一个定制化的 getAt/putAt 实现,是一种方便的解析对象的方式:
class User {
Long id
String name
def getAt(int i) { //1
switch (i) {
case 0: return id
case 1: return name
}
throw new IllegalArgumentException("No such element $i")
}
void putAt(int i, def value) { //2
switch (i) {
case 0: id = value; return
case 1: name = value; return
}
throw new IllegalArgumentException("No such element $i")
}
}
def user = new User(id: 1, name: 'Alex') //3
assert user[0] == 1 //4
assert user[1] == 'Alex' //5
user[1] = 'Bob' //6
assert user.name == 'Bob' //7
//1: User 类型定义了一个定制化的 getAt 实现
//2: User 类型定义了一个定制化的 putAt 实现
//3: 创建一个 User 对象
//4: 使用下标操作符和索引 0 来获取用户的 id
//5: 使用下标操作符和索引 1 来获取用户的 name
//6: 可以使用下标操作符来给属性赋值,这都归功于底层对 putAt 方法的调用
//7: 校验 name 属性的确发生了变化
8.5 成员关系操作符
成员关系操作符(in)就等价于调用 isCase 方法。具体到一个 List 对象,它就等效于调用 contains 方法,请看下例:
def list = ['Grace','Rob','Emmy']
assert ('Emmy' in list) //1
//1: 等价于调用 list.contains('Emmy') 或 list.isCase('Emmy')
8.6 身份操作符
在 Groovy 中使用 == 进行相等性测试和 Java 中是有区别的。在 Groovy 中,它实际会调用 equals 方法。如果你想比较引用的相等性,你应该像下面的例子一样使用 is 操作符:
def list1 = ['Groovy 1.8','Groovy 2.0','Groovy 2.3'] //1
def list2 = ['Groovy 1.8','Groovy 2.0','Groovy 2.3'] //2
assert list1 == list2 //3
assert !list1.is(list2) //4
//1: 创建一个字符串列表
//2: 创建另一个包含相同元素的字符串列表
//3: 使用 ==,我们是在测试对象的相等性
//4: 使用 is,我们是在检查引用的相等性
8.7 强制转换操作符
强制转换操作符(as)是强制类型转换的变种。它将对象强制地由一种类型转换为另一种类型,而不需要这两种类型具有可赋值性。我们看个例子:
Integer x = 123
String s = (String) x //1
//1: Integer 和 String 类型之间不具有可赋值性,因此运行时该处将抛出 ClassCastException
可以通过强制转换来修复该问题:
Integer x = 123
String s = x as String //1
//1: Integer 和 String 类型之间不具有可赋值性,但是使用 as 将会把这个整形强制转换为一个字符串
当一个对象被强制转换成另一个类型的对象时,除非目标类型和源类型相同,否则强制转换都将会返回一个新的对象。强制转换的规则因着不同的源和目标类型而不同,并且如果两个类型之间没有转换规则的话,强制转换也可能会失败。定制化的转换规则可以使用 asType 方法来实现:
class Identifiable {
String name
}
class User {
Long id
String name
def asType(Class target) { //1
if (target == Identifiable) {
return new Identifiable(name: name)
}
throw new ClassCastException("User cannot be coerced into $target")
}
}
def u = new User(name: 'Xavier') //2
def p = u as Identifiable //3
assert p instanceof Identifiable //4
assert !(p instanceof User) //5
//1: User 类中定义了一个从 User 到 Identifiable 类的转换规则
//2: 创建一个 User 类的实例
//3: 将 User 实例强制转换成 Identifiable 类型的对象
//4: 强制转换后的对象是 Identifiable 类的实例
//5: 强制转换后的对象不再是 User 类的实例
8.8 钻石操作符
钻石操作符(<>)只是一个语法糖操作符,它的引入只是为了兼容 Java 7 中的同名操作符。它用来表明泛型类型应该从声明中推导:
List<String> strings = new LinkedList<>()
在动态类型的 Groovy 中,这个操作符完全用不到。在静态类型检查的 Groovy 中,该操作符也是可选的,因为无论该操作符是否存在,Groovy 类型检查器都会执行类型推断。
8.9 方法调用操作符
方法调用操作符 () 被用来隐式地调用一个名叫 call 的方法。对于任意一个定义了 call 方法的对象,你都可以省略 .call 部分,而以方法调用操作符 () 代之:
class MyCallable {
int call(int x) { //1
2*x
}
}
def mc = new MyCallable()
assert mc.call(2) == 4 //2
assert mc(2) == 4 //3
//1: MyCallable 类定义了一个叫做 call 的方法。请注意,它不需要实现 java.util.concurrent.Callable 接口
//2: 使用常规的方法调用语法来调用 call 方法
//3: 可以省略 .call 部分,这都归功于方法调用操作符
9. 操作符优先级
下面的表格按照优先级顺序列出了所以 Groovy 操作符:
优先级 | 操作符 | 名称 |
---|---|---|
1 |
| 对象创建,显式括号 |
| 方法调用,闭包,列表/映射字面量 | |
| 成员访问,方法指针,字段/属性直接访问 | |
| 安全导航,展开,展开点,展开映射 | |
| 按位取反/模式,逻辑非,类型转换 | |
| 类标/映射/数组索引,后缀自增/自减 | |
2 |
| 乘方 |
3 |
| 前缀自增/自减,正号,负号 |
4 |
| 乘,除,取余 |
5 |
| 加,减 |
6 |
| 左移/右移,无符号右移,闭区间,左闭右开区间 |
7 |
| 小于,小于等于,大于,大于等于,成员操作符,实例判断,强制类型转换 |
8 |
| 等于,不等于,比较 |
| 正则查找,正则匹配 | |
9 |
| 位与 |
10 |
| 位异或 |
11 |
| 位或 |
12 |
| 逻辑与 |
13 |
| 逻辑或 |
14 |
| 三目运算符 |
| 埃尔维斯运算符 | |
15 |
| 各种赋值运算符 |
10. 操作符重载
Groovy 允许你重载各种操作符,以便你能够在自定义类中使用它们。请看下面这个简单的类:
class Bucket {
int size
Bucket(int size) { this.size = size }
Bucket plus(Bucket other) { //1
return new Bucket(this.size + other.size)
}
}
//1: Bucket 类实现了一个名为 plus 的特殊方法
仅仅通过实现 plus() 方法,现在 Bucket 类就可以像下面这样使用加法操作符:
def b1 = new Bucket(4)
def b2 = new Bucket(11)
assert (b1 + b2).size == 15 //1
//1: 可以使用加法操作符 + 对两个 Bucket 对象进行相加
所有非比较型的 Groovy 操作符都有一个与之相关联的方法,你可以在自己的类中按需实现这些方法。唯一的要求是,该方法要是公有的,有正确的名称和正确个数的参数。而参数类型就依赖于你想在操作符右侧支持哪些类型了。例如你可以支持下面这样的调用:
assert (b1 + 11).size == 15
这只需要实现一个具有以下签名的 plus() 方法即可:
Bucket plus(int capacity) {
return new Bucket(this.size + capacity)
}
下面是操作符和其关联的方法名的完整列表:
操作符 | 关联方法 | 操作符 | 关联方法 |
---|---|---|---|
| a.plus(b) |
| a.getAt(b) |
| a.minus(b) |
| a.putAt(b, c) |
| a.multiply(b) |
| b.isCase(a) |
| a.div(b) |
| a.leftShift(b) |
| a.mod(b) |
| a.rightShift(b) |
| a.power(b) |
| a.rightShiftUnsigned(b) |
| a.or(b) |
| a.next() |
| a.and(b) |
| a.previous() |
| a.xor(b) |
| a.positive() |
| a.asType(b) |
| a.negative() |
| a.call() |
| a.bitwiseNegate() |