运算符

本章介绍 Groovy 编程语言的运算符。

1. 算术运算符

Groovy 支持在数学和其他编程语言(如 Java)中常见的算术运算符。所有 Java 算术运算符都受支持。让我们通过以下示例来了解它们。

1.1. 普通算术运算符

Groovy 中提供了以下二元算术运算符

运算符 用途 备注

+

加法

-

减法

*

乘法

/

除法

对于整数除法,使用 intdiv(),有关除法返回类型的更多信息,请参阅关于整数除法的部分。

%

余数

**

有关操作返回类型的更多信息,请参阅关于幂运算的部分。

以下是这些运算符的一些使用示例

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 真“或”假为真

4. 位运算符和位移运算符

4.1. 位运算符

Groovy 提供了四个位运算符

  • &: 位“与”

  • |: 位“或”

  • ^: 位“异或”(异或)

  • ~: 位非

位运算符可以应用于 byteshortintlongBigInteger 类型的参数。如果其中一个参数是 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 提供了三个位移运算符

  • <<: 左移

  • >>: 右移

  • >>>: 无符号右移

所有这三个运算符都适用于左参数类型为 byteshortintlong 的情况。前两个运算符也可以应用于左参数类型为 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. 非运算符

“非”运算符用感叹号 (!) 表示,并反转底层布尔表达式的结果。特别是,可以将 not 运算符与 Groovy truth 结合使用

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 truth 兼容,因此您可以使其更简单

result = string ? 'Found' : 'Not found'

5.3. Elvis 运算符

“Elvis 运算符”是三元运算符的缩写。它方便的一个实例是,如果表达式解析为 false-ish(如 Groovy truth 中所述),则返回“ sensible default”值。一个简单的示例可能如下所示

displayName = user.name ? user.name : 'Anonymous'   (1)
displayName = user.name ?: 'Anonymous'              (2)
1 使用三元运算符,您必须重复要赋值的值
2 使用 Elvis 运算符,如果被测试的值不是 false-ish,则使用该值

使用 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 使用 null 安全运算符可防止 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 定义一个接受 String 作为参数的重载 doSomething 方法
2 定义一个接受 Integer 作为参数的重载 doSomething 方法
3 doSomething 上创建一个方法指针,不指定参数类型
4 使用带有 String 的方法指针会调用 doSomethingString 版本
5 使用带有 Integer 的方法指针会调用 doSomethingInteger 版本

为了与 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,因此当 =~ 运算符作为谓词(在 if?: 等中)出现时,它与 Perl 的 =~ 运算符的简单用法一致。当目的是迭代指定模式的匹配(在 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,尽管通常建议保留显式展开点运算符。

展开运算符是 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 使用展开运算符将抛出 NullPointerException
3 接收者也可能为 null,在这种情况下返回值为 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. 展开 Map 元素

展开 Map 运算符的作用方式与展开列表运算符类似,但用于 Map。它允许您将一个 Map 的内容内联到另一个 Map 字面量中,如下例所示

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 是我们想要内联的 Map
2 我们使用 *:m1 符号将 m1 的内容展开到 map
3 map 包含 m1 的所有元素

展开 Map 运算符的位置是相关的,如下例所示

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 是我们想要内联的 Map
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. 下标运算符

下标运算符是 getAtputAt 的简写表示法,取决于它出现在赋值的左侧还是右侧

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 == list2list1 != 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 类定义了一个从 UserIdentifiable 的自定义转换规则
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 我们可以使用经典的方法调用语法来调用该方法
3 或者我们可以借助调用运算符省略 .call

9. 运算符优先级

下表列出了所有 Groovy 运算符的优先级顺序。

级别 运算符 名称

1

new   ()

对象创建,显式括号

()   {}   []

方法调用,闭包,列表/映射字面量

.   .&   .@

成员访问,方法闭包,字段/属性访问

?.   *   *.   *:

安全解引用,展开,展开点,展开映射

~   !   (type)

位非/模式,非,类型转换

[]   ?[]   ++   --

列表/映射/数组(安全)索引,后递增/递减

2

**

3

+` {nbsp} `--` {nbsp} `   -

前递增/递减,一元加,一元减

4

*   /   %

乘,除,余

5

+   -

加法,减法

6

<<   >>   >>>   ..   ..<   <..<   <.

左/右(无符号)移位,包含/不包含范围

7

<   <=   >   >=   in   !in   instanceof   !instanceof   as

小于/大于/或等于,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[b]

a.getAt(b)

-

a.minus(b)

a[b] = c

a.putAt(b, c)

*

a.multiply(b)

a in 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

a.positive()

as

a.asType(b)

-a

a.negative()

a()

a.call()

~a

a.bitwiseNegate()