本章介绍 Groovy 编程语言的运算符。
1. 算术运算符
Groovy 支持你在数学和其他编程语言(如 Java)中找到的常用算术运算符。所有 Java 算术运算符都受支持。让我们在以下示例中逐一了解它们。
1.1. 普通算术运算符
Groovy 提供以下二元算术运算符
运算符 | 用途 | 备注 |
---|---|---|
|
加法 |
|
|
减法 |
|
|
乘法 |
|
|
除法 |
使用 |
|
取余 |
|
|
乘方 |
查看有关 乘方运算 的部分以了解有关运算返回值类型的更多信息。 |
以下是使用这些运算符的一些示例
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 | 注意使用括号将表达式括起来以将一元减号应用于该括起来的表达式。 |
在涉及一元算术运算符方面,++
(增量)和 --
(减量)运算符都可用,它们既可以是前缀形式,也可以是后缀形式
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 | 后缀增量将在表达式求值并赋值到 b 之后增量 a |
2 | 后缀减量将在表达式求值并赋值到 d 之后减量 c |
3 | 前缀增量将在表达式求值并赋值到 f 之前增量 e |
4 | 前缀减量将在表达式求值并赋值到 h 之前减量 g |
有关布尔值上的非运算符,请参阅 条件运算符。
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 3.0.0 起) |
|
不同(自 Groovy 3.0.0 起) |
以下是一些使用这些运算符进行简单数字比较的示例
assert 1 + 2 == 3
assert 3 != 4
assert -2 < 3
assert 2 <= 2
assert 3 <= 4
assert 5 > 1
assert 5 >= -2
===
和 !==
都受支持,它们分别与调用 is()
方法和否定调用 is()
方法相同。
import groovy.transform.EqualsAndHashCode
@EqualsAndHashCode
class Creature { String type }
def cat = new Creature(type: 'cat')
def copyCat = cat
def lion = new Creature(type: 'cat')
assert cat.equals(lion) // Java logical equality
assert cat == lion // Groovy shorthand operator
assert cat.is(copyCat) // Groovy identity
assert cat === copyCat // operator shorthand
assert cat !== lion // negated operator shorthand
3. 逻辑运算符
Groovy 为布尔表达式提供了三个逻辑运算符
-
&&
: 逻辑“与” -
||
: 逻辑“或” -
!
: 逻辑“非”
让我们用以下示例来说明它们
assert !false (1)
assert true && true (2)
assert true || false (3)
1 | “非”假为真 |
2 | 真“与”真为真 |
3 | 真“或”假为真 |
3.1. 优先级
逻辑“非”的优先级高于逻辑“与”。
assert (!false && false) == false (1)
1 | 此处,断言为真(因为括号内的表达式为假),因为“非”的优先级高于“与”,因此它只应用于第一个“假”项;否则,它将应用于“与”的结果,将其变为真,并且断言将失败 |
逻辑“与”的优先级高于逻辑“或”。
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 | 我们创建一个函数,每当被调用时,它都会将 called 标志设置为真 |
2 | 在第一种情况下,在重置 called 标志后,我们确认如果 || 的左操作数为真,则不会调用该函数,因为 || 会短路计算右操作数 |
3 | 在第二种情况下,左操作数为假,因此调用了该函数,如我们的标志现在为真所表明的那样 |
4 | 同样,对于 && ,我们确认使用假的左操作数不会调用该函数 |
5 | 但是使用真的左操作数会调用该函数 |
4. 位运算和位移运算符
4.1. 位运算符
Groovy 提供四个位运算符
-
&
: 位“与” -
|
: 位“或” -
^
: 位“异或”(排他“或”) -
~
: 位取反
位运算符可以应用于类型为 byte
、short
、int
、long
或 BigInteger
的参数。如果其中一个参数是 BigInteger
,则结果将是 BigInteger
类型;否则,如果其中一个参数是 long
,则结果将是 long
类型;否则,结果将是 int
类型
int a = 0b00101010
assert a == 42
int b = 0b00001000
assert b == 8
assert (a & a) == a (1)
assert (a & b) == b (2)
assert (a | a) == a (3)
assert (a | b) == a (4)
int mask = 0b11111111 (5)
assert ((a ^ a) & mask) == 0b00000000 (6)
assert ((a ^ b) & mask) == 0b00100010 (7)
assert ((~a) & mask) == 0b11010101 (8)
1 | 位与 |
2 | 位与返回公共位 |
3 | 位或 |
4 | 位或返回所有“1”位 |
5 | 设置掩码以仅检查最后 8 位 |
6 | 位异或自身返回 0 |
7 | 位异或 |
8 | 位取反 |
值得注意的是,基本类型的内部表示遵循 Java 语言规范。特别是,基本类型是有符号的,这意味着对于位取反,最好始终使用掩码来检索仅所需的位。
在 Groovy 中,位运算符是 可重载 的,这意味着你可以为任何类型的对象定义这些运算符的行为。
4.2. 位移运算符
Groovy 提供三个位移运算符
-
<<
: 左移 -
>>
: 右移 -
>>>
: 无符号右移
所有三个运算符都适用于左参数类型为 byte
、short
、int
或 long
的情况。前两个运算符也可以应用于左参数类型为 BigInteger
的情况。如果左参数是 BigInteger
,则结果将是 BigInteger
类型;否则,如果左参数是 long
,则结果将是 long
类型;否则,结果将是 int
类型
assert 12.equals(3 << 2) (1)
assert 24L.equals(3L << 3) (1)
assert 48G.equals(3G << 4) (1)
assert 4095 == -200 >>> 20
assert -1 == -200 >> 20
assert 2G == 5G >> 1
assert -3G == -5G >> 1
1 | 使用 equals 方法而不是 == 来确认结果类型 |
在 Groovy 中,位移运算符是 可重载 的,这意味着你可以为任何类型的对象定义这些运算符的行为。
5. 条件运算符
5.1. 非运算符
“非”运算符用感叹号 (!
) 表示,并反转底层布尔表达式的结果。特别是,可以将 非
运算符与 Groovy 真值 结合使用
assert (!true) == false (1)
assert (!'foo') == false (2)
assert (!'') == true (3)
1 | true 的否定为 false |
2 | “foo”是非空字符串,求值为 true ,因此否定返回 false |
3 | '' 是空字符串,求值为 false ,因此否定返回 true |
5.2. 三元运算符
三元运算符是一种快捷表达式,等效于将某个值分配给变量的 if/else 分支。
而不是
if (string!=null && string.length()>0) {
result = 'Found'
} else {
result = 'Not found'
}
你可以写
result = (string!=null && string.length()>0) ? 'Found' : 'Not found'
三元运算符也与 Groovy 真值 兼容,因此你可以使其更简单
result = string ? 'Found' : 'Not found'
5.3. Elvis 运算符
“Elvis 运算符”是对三元运算符的简写。这在返回“明智的默认”值(如果表达式解析为 false
类(如 Groovy 真值 中)时非常有用。一个简单的示例可能如下所示
displayName = user.name ? user.name : 'Anonymous' (1)
displayName = user.name ?: 'Anonymous' (2)
1 | 使用三元运算符,你必须重复要分配的值 |
2 | 使用 Elvis 运算符,如果该值不是 false 类,则使用该值 |
使用 Elvis 运算符可以减少代码的冗长性,并通过消除在条件和正返回值中都需要测试表达式的重复,从而减少重构时出错的风险。
5.4. Elvis 赋值运算符
Groovy 3.0.0 引入了 Elvis 运算符,例如
import groovy.transform.ToString
@ToString(includePackage = false)
class Element {
String name
int atomicNumber
}
def he = new Element(name: 'Helium')
he.with {
name = name ?: 'Hydrogen' // existing Elvis operator
atomicNumber ?= 2 // new Elvis assignment shorthand
}
assert he.toString() == 'Element(Helium, 2)'
6. 对象运算符
6.1. 安全导航运算符
安全导航运算符用于避免 NullPointerException
。通常,当你对某个对象的引用时,你可能需要在访问该对象的函数或属性之前验证它是否为 null
。为了避免这种情况,安全导航运算符将简单地返回 null
而不是抛出异常,例如
def person = Person.find { it.id == 123 } (1)
def name = person?.name (2)
assert name == null (3)
1 | find 将返回一个 null 实例 |
2 | 使用空安全运算符可防止 NullPointerException |
3 | 结果为 null |
6.2. 直接字段访问运算符
在 Groovy 中,通常当你写这样的代码时
class User {
public final String name (1)
User(String name) { this.name = name}
String getName() { "Name: $name" } (2)
}
def user = new User('Bob')
assert user.name == 'Name: Bob' (3)
1 | 公共字段 name |
2 | 一个返回自定义字符串的 name 的 getter |
3 | 调用 getter |
user.name
调用会触发对同名属性的调用,也就是说,在这里,调用 name
的 getter。如果你想获取字段而不是调用 getter,你可以使用直接字段访问运算符
assert user.@name == 'Bob' (1)
1 | 使用 .@ 强制使用字段而不是 getter |
6.3. 方法指针运算符
方法指针运算符 (.&
) 可用于将方法的引用存储在变量中,以便稍后调用它
def str = 'example of method reference' (1)
def fun = str.&toUpperCase (2)
def upper = fun() (3)
assert upper == str.toUpperCase() (4)
1 | str 变量包含一个 String |
2 | 我们将 str 实例上的 toUpperCase 方法的引用存储在一个名为 fun 的变量中 |
3 | fun 可以像普通方法一样被调用 |
4 | 我们可以检查结果是否与我们直接在 str 上调用它时相同 |
使用方法指针有很多优点。首先,这种方法指针的类型是 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 并返回一个 String |
3 | 我们在该函数上创建一个方法指针 |
4 | 我们创建要收集描述符的元素列表 |
5 | 方法指针可以在预期使用 Closure 的地方使用 |
方法指针由接收器和方法名绑定。参数在运行时解析,这意味着如果有多个同名方法,语法没有区别,只有在运行时才会解析要调用的适当方法
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 方法,接受一个 String 作为参数 |
2 | 定义一个重载的 doSomething 方法,接受一个 Integer 作为参数 |
3 | 在 doSomething 上创建一个单个方法指针,不指定参数类型 |
4 | 使用 String 的方法指针调用 doSomething 的 String 版本 |
5 | 使用 Integer 的方法指针调用 doSomething 的 Integer 版本 |
为了与 Java 8 方法引用期望保持一致,在 Groovy 3 及更高版本中,可以使用 new
作为方法名来获取指向构造函数的方法指针
def foo = BigInteger.&new
def fortyTwo = foo('42')
assert fortyTwo == 42G
同样在 Groovy 3 及更高版本中,你可以获取指向类中实例方法的方法指针。此方法指针将接收实例作为附加参数,以便在该实例上调用方法。
def instanceMethod = String.&toUpperCase
assert instanceMethod('foo') == 'FOO'
为了向后兼容,对于这种情况,任何恰好具有正确参数的静态方法都将优先于实例方法。
6.4. 方法引用运算符
Groovy 3+ 中的 Parrot 解析器支持 Java 8+ 方法引用运算符。方法引用运算符 (::
) 可用于在预期使用函数接口的上下文中引用方法或构造函数。这在一定程度上与 Groovy 的方法指针运算符提供的功能重叠。实际上,对于动态 Groovy,方法引用运算符只是方法指针运算符的别名。对于静态 Groovy,运算符产生的字节码类似于 Java 在相同上下文中产生的字节码。
以下脚本展示了一些突出显示各种受支持方法引用情况的示例
import groovy.transform.CompileStatic
import static java.util.stream.Collectors.toList
@CompileStatic
void methodRefs() {
assert 6G == [1G, 2G, 3G].stream().reduce(0G, BigInteger::add) (1)
assert [4G, 5G, 6G] == [1G, 2G, 3G].stream().map(3G::add).collect(toList()) (2)
assert [1G, 2G, 3G] == [1L, 2L, 3L].stream().map(BigInteger::valueOf).collect(toList()) (3)
assert [1G, 2G, 3G] == [1L, 2L, 3L].stream().map(3G::valueOf).collect(toList()) (4)
}
methodRefs()
1 | 类实例方法引用:add(BigInteger val) 是 BigInteger 中的实例方法 |
2 | 对象实例方法引用:add(BigInteger val) 是对象 3G 的实例方法 |
3 | 类静态方法引用:valueOf(long val) 是类 BigInteger 的静态方法 |
4 | 对象静态方法引用:valueOf(long val) 是对象 3G 的静态方法(在正常情况下,有些人认为这是一种不好的风格) |
以下脚本展示了一些突出显示各种受支持构造函数引用情况的示例
@CompileStatic
void constructorRefs() {
assert [1, 2, 3] == ['1', '2', '3'].stream().map(Integer::valueOf).collect(toList()) (1)
def result = [1, 2, 3].stream().toArray(Integer[]::new) (2)
assert result instanceof Integer[]
assert result.toString() == '[1, 2, 3]'
}
constructorRefs()
1 | 类构造函数引用 |
2 | 数组构造函数引用 |
7. 正则表达式运算符
7.1. 模式运算符
模式运算符 (~
) 提供了一种创建 java.util.regex.Pattern
实例的简单方法
def p = ~/foo/
assert p instanceof Pattern
虽然通常你会在斜杠字符串中的表达式中找到模式运算符,但它可以在 Groovy 中与任何类型的 String
一起使用
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 变量的匹配器 |
2 | =~ 的返回类型是 Matcher |
3 | 等同于调用 if (!m.find(0)) |
由于 Matcher
通过调用其 find
方法强制转换为 boolean
,因此 =~
运算符与 Perl 的 =~
运算符的简单使用一致,当它作为谓词(在 if
、?:
等中)出现时。当意图是迭代指定模式的匹配项(在 while
等中)时,直接在匹配器上调用 find()
或使用 iterator
DGM。
7.3. 匹配运算符
匹配运算符 (==~
) 是查找运算符的轻微变体,它不返回 Matcher
,而是返回一个布尔值,并且需要对输入字符串进行严格匹配
m = text ==~ /match/ (1)
assert m instanceof Boolean (2)
if (m) { (3)
throw new RuntimeException("Should not reach that point!")
}
1 | ==~ 将主题与正则表达式匹配,但匹配必须是严格的 |
2 | 因此,==~ 的返回类型是 boolean |
3 | 等同于调用 if (text ==~ /match/) |
7.4. 比较查找运算符与匹配运算符
通常,当模式涉及单个精确匹配时,使用匹配运算符,否则查找运算符可能更有用。
assert 'two words' ==~ /\S+\s+\S+/
assert 'two words' ==~ /^\S+\s+\S+$/ (1)
assert !(' leading space' ==~ /\S+\s+\S+/) (2)
def m1 = 'two words' =~ /^\S+\s+\S+$/
assert m1.size() == 1 (3)
def m2 = 'now three words' =~ /^\S+\s+\S+$/ (4)
assert m2.size() == 0 (5)
def m3 = 'now three words' =~ /\S+\s+\S+/
assert m3.size() == 1 (6)
assert m3[0] == 'now three'
def m4 = ' leading space' =~ /\S+\s+\S+/
assert m4.size() == 1 (7)
assert m4[0] == 'leading space'
def m5 = 'and with four words' =~ /\S+\s+\S+/
assert m5.size() == 2 (8)
assert m5[0] == 'and with'
assert m5[1] == 'four words'
1 | 等效,但显式 ^ 和 $ 不鼓励使用,因为它们不需要 |
2 | 没有匹配,因为有前导空格 |
3 | 一个匹配 |
4 | ^ 和 $ 表示需要精确匹配 |
5 | 零匹配 |
6 | 一个匹配,贪婪地从第一个词开始 |
7 | 一个匹配,忽略前导空格 |
8 | 两个匹配 |
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 项的集合 |
表达式 cars*.make
等同于 cars.collect{ it.make }
。Groovy 的 GPath 表示法允许在引用属性不是包含列表的属性时使用快捷方式,在这种情况下,它会自动展开。在前面提到的情况下,可以使用表达式 cars.make
,尽管通常建议保留显式的展开点运算符。
展开运算符是空安全的,这意味着如果集合中的元素为空,它将返回空而不是抛出 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 | 使用展开运算符不会抛出 NullPointerException |
3 | 接收器也可能为空,在这种情况下,返回值为 null |
展开运算符可以在实现 Iterable
接口的任何类上使用
class Component {
Integer 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'] // flatten one level
assert models.flatten() == ['408', '508', 'Clio', 'Captur'] // flatten all levels (one in this case)
对于集合的集合,请考虑使用 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 包含所有预期的键,但 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).collect() == [1, 2, 3, 4, 5] (4)
assert (0<..<5).collect() == [1, 2, 3, 4] (5)
assert (0..5) instanceof List (6)
assert (0..5).size() == 6 (7)
1 | 一个简单的整数范围,存储在一个局部变量中 |
2 | 一个 IntRange ,具有包含边界 |
3 | 一个 IntRange ,具有排他上限 |
4 | 一个 IntRange ,具有排他下限 |
5 | 一个 IntRange ,具有排他下限和上限 |
6 | 一个 groovy.lang.Range 实现了 List 接口 |
7 | 这意味着您可以对其调用 size 方法 |
范围的实现是轻量级的,这意味着只有上下限被存储。您可以从任何具有 next()
和 previous()
方法的 Comparable
对象创建范围,以确定范围中的下一个/上一个项目。例如,您可以用这种方式创建一个字符范围
assert ('a'..'d').collect() == ['a','b','c','d']
8.3. 空间船操作符
空间船操作符 (<=>
) 委托给 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 也支持范围 |
4 | putAt 也支持 |
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 | 创建一个示例用户 |
4 | 使用索引 0 的下标操作符可以检索用户 ID |
5 | 使用索引 1 的下标操作符可以检索用户名 |
6 | 我们可以使用下标操作符来写入属性,因为委托给了 putAt |
7 | 并检查它确实是属性 name 被更改了 |
8.5. 安全索引操作符
Groovy 3.0.0 引入了安全索引操作符,即 ?[]
,它类似于 ?.
。例如
String[] array = ['a', 'b']
assert 'b' == array?[1] // get using normal array index
array?[1] = 'c' // set using normal array index
assert 'c' == array?[1]
array = null
assert null == array?[1] // return null for all index values
array?[1] = 'c' // quietly ignore attempt to set value
assert null == array?[1]
def personInfo = [name: 'Daniel.Sun', location: 'Shanghai']
assert 'Daniel.Sun' == personInfo?['name'] // get using normal map index
personInfo?['name'] = 'sunlan' // set using normal map index
assert 'sunlan' == personInfo?['name']
personInfo = null
assert null == personInfo?['name'] // return null for all map values
personInfo?['name'] = 'sunlan' // quietly ignore attempt to set value
assert null == personInfo?['name']
8.6. 成员资格操作符
成员资格操作符 (in
) 等效于调用 isCase
方法。在 List
的上下文中,它等效于调用 contains
,如以下示例所示
def list = ['Grace','Rob','Emmy']
assert ('Emmy' in list) (1)
assert ('Alex' !in list) (2)
1 | 等效于调用 list.contains('Emmy') 或 list.isCase('Emmy') |
2 | 成员资格否定等效于调用 !list.contains('Emmy') 或 !list.isCase('Emmy') |
8.7. 身份操作符
在 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)
assert list1 !== list2 (5)
1 | 创建一个字符串列表 |
2 | 创建另一个包含相同元素的字符串列表 |
3 | 使用 == ,我们测试对象相等性,等效于 Java 中的 list1.equals(list2) |
4 | 使用 is ,我们可以检查引用是否不同,等效于 Java 中的 list1 == list2 |
5 | 使用 === 或 !== (从 Groovy 3.0.0 开始支持并推荐),我们也可以检查引用是否不同,等效于 Java 中的 list1 == list2 和 list1 != list2 |
8.8. 强制转换操作符
强制转换操作符 (as
) 是强制转换的变体。强制转换将对象从一种类型转换为另一种类型,**无需**它们兼容赋值。让我们举个例子
String input = '42'
Integer num = (Integer) input (1)
1 | String 不能赋值给 Integer ,因此它将在运行时产生 ClassCastException |
这可以通过使用强制转换来解决
String input = '42'
Integer num = input as Integer (1)
1 | String 不能赋值给 Integer ,但使用 as 将将其强制转换为 Integer |
当一个对象被强制转换为另一个对象时,除非目标类型与源类型相同,否则强制转换将返回一个**新的**对象。强制转换的规则根据源类型和目标类型而不同,如果找不到转换规则,则强制转换可能会失败。自定义转换规则可以通过 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.9. 菱形操作符
菱形操作符 (<>
) 只是一个语法糖操作符,用于支持与 Java 7 中同名操作符的兼容性。它用于指示泛型类型应该从声明中推断出来
List<String> strings = new LinkedList<>()
在动态 Groovy 中,这完全没有用。在静态类型检查的 Groovy 中,它也是可选的,因为 Groovy 类型检查器无论此操作符是否存在都会执行类型推断。
8.10. 调用操作符
调用操作符 ()
用于隐式调用名为 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 | 我们可以使用经典的 method call 语法调用该方法 |
3 | 或者,我们可以通过调用操作符省略 .call |
9. 操作符优先级
下表按优先级列出了所有 Groovy 操作符。
级别 | 操作符(s) | 名称(s) |
---|---|---|
1 |
|
对象创建,显式括号 |
|
方法调用,闭包,字面列表/映射 |
|
|
成员访问,方法闭包,字段/属性访问 |
|
|
安全取消引用,传播,传播点,传播映射 |
|
|
按位取反/模式,非,类型转换 |
|
|
列表/映射/数组(安全)索引,后递增/递减 |
|
2 |
|
乘方 |
3 |
|
前递增/递减,一元加,一元减 |
4 |
|
乘法,除法,余数 |
5 |
|
加法,减法 |
6 |
|
左/右(无符号)移位,包含/排除范围 |
7 |
|
小于/大于/等于,in,not in,instanceof,not instanceof,类型强制转换 |
8 |
|
等于,不等于,比较,相同,不相同 |
|
正则表达式查找,正则表达式匹配 |
|
9 |
|
二进制/按位与 |
10 |
|
二进制/按位异或 |
11 |
|
二进制/按位或 |
12 |
|
逻辑与 |
13 |
|
逻辑或 |
14 |
|
三元条件 |
|
Elvis 操作符 |
|
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() |