Groovy运算符


Groovy运算符

Groovy运算符阅读笔记,非全文翻译。

1.算术运算符

Groovy支持常见的算术运算符,和所有Java支持的运算符。

1.1.普通算术运算符

Groovy支持的二元运算符:

运算符 用途 备注
+
-
*
/ 整数除法用的是intdiv(),更多的除法返回值类型参看integer division
% 取余
** 乘方 参看the power operation了解更多关于乘方返回值类型

1.2.一元运算符

+-都可以作为一元运算符。并支持++--方式的前缀和后缀

1.3.赋值运算符

上面的二元运算符也可以作为赋值运算符使用:

  • +=

  • -=

  • *=

  • /=

  • %=

  • **=

2.关系运算符

操作符 用途
== 相等
!= 不相等
< 小于
<= 小于等于
> 大于
>= 大于等于
=== 完全相同
!== 不完全相同

===!==的效果等同于调用is()方法。

在Groovy中 == 操作符是调用了对象的equals()方法做比较,===操作符是比较对象的内存地址是否相同

而在Java中,==是比较对象的地址,如果需要比较对象的值是否相同则是调用对象的equals()方法做比较。并且该对象需要重写equals()hashCode()方法。

3.逻辑运算符

逻辑运算符有&&,||,!

3.1.优先级

! > && > ||

Groovy
assert (!false && false) == false // ! > &&, 结果为false

assert true || true && false // && > ||, 结果为true

3.2.短路

对于||, 当运算符左边的值为true时,不会计算右边的值。只有左边的值为false时才会计算有百年的值。
对于&&, 只有当左边的值为ture时才会计算右边的值。

4.位运算符

Groovy提供了四种位运算符

  • & : 按位和

  • | : 按位或

  • ^ : 按位异或

  • ~ : 按位取反

Groovy的基础类型遵循Java的规范,基础类型都是有符号数,所以在使用按位取反的时候,使用mask来对指定的位置取反。

5.条件运算符

5.1.否操作符

使用!反转结果

5.2.三元运算符

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

string?判断字符串是否为空

5.3.Elvis操作符

Groovy 3.0.0引入了Elvis操作符,这是对三元表达式的进一步简化:

Groovy
import groovy.transform.ToString

@ToString
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.

Groovy
def person = Person.find { it.id == 123 }
def name = person?.name
assert name == null

6.2.直接访问操作符

Groovy
class User {
    public final String name
    User(String name) { this.name = name}
    String getName() { "Name: $name" }
}
def user = new User('Bob')
assert user.name == 'Name: Bob'

对于上面的例子,在访问name属性时实际是调用了对应的get方法。如果想要直接获取字段而不是调用方法,可以使用.@操作符。
通过.@可以直接访问字段而不是通过get方法

6.3.方法指针操作符

Groovy
def str = 'example of method reference'
def fun = str.&toUpperCase // 将str的toUpperCase方法整体作为一个变量保存在fun中
def upper = fun() // 像调用常规方法一样调用fun方法
assert upper == str.toUpperCase() // fun方法的结果和str.toUpperCase()结果相同

方法指针是groovy.lang.Closure类型的,因此可以在任何使用闭包的位置使用。

Groovy
def transform(List elements, Closure action) {
    def result = []
    elements.each {
        result << action(it)
    }
    result
}

//定义describe方法,作用是输出Person的信息
String describe(Person p) {
    "$p.name is $p.age"
}

//方法指针保存在action中
def action = this.&describe
def list = [
    new Person(name: 'Bob',   age: 42),
    new Person(name: 'Julia', age: 35)]
//把方法指针作为闭包传入方法
assert transform(list, action) == ['Bob is 42', 'Julia is 35']

方法指针与接收者和方法名绑定。对于重载方法,在运行时Groovy会根据传参选择正确的方法。

Groovy
def doSomething(String str) { str.toUpperCase() }
def doSomething(Integer x) { 2*x }
def reference = this.&doSomething
assert reference('foo') == 'FOO'
assert reference(123)   == 246

在Groovy3.0及以上,可以使用new作为一个方法名来指向一个构造函数。
例如:

Groovy
def foo  = BigInteger.&new
def fortyTwo = foo('42')
assert fortyTwo == 42G

在Groovy3.0及以上,可以使用方法指针指向一个类的实例方法,这个方法指针接收一个接收者类型的实例作为参数。例如:

Groovy
def instanceMethod = String.&toUpperCase // 指向实例方法的方法指针
assert instanceMethod('foo') == 'FOO' // 方法指针接收一个实例

总结一下:

  • 方法指针可以指向一个具体实例的方法,例如

    Groovy
    def str = 'example of method reference'
    def fun = str.&toUpperCase
  • 方法指针可以指向当前脚本的方法,例如:

    Groovy
    String describe(Person p) {
    "$p.name is $p.age"
    }
    def action = this.&describe
  • 方法指针可以指向多个重载方法,在运行时根据传参选择正确的方法执行

  • 方法指针可以使用new来指向一个构造方法

  • 方法指针可以指向一个实例方法。

6.4.方法引用运算符

在Groovy 3.0及以上支持Java8以上的::运算符。

vGroovy
import groovy.transform.CompileStatic
import static java.util.stream.Collectors.toList

@CompileStatic
void methodRefs() {
    assert 6G == [1G, 2G, 3G].stream().reduce(0G, BigInteger::add)

    assert [4G, 5G, 6G] == [1G, 2G, 3G].stream().map(3G::add).collect(toList())

    assert [1G, 2G, 3G] == [1L, 2L, 3L].stream().map(BigInteger::valueOf).collect(toList())  

    assert [1G, 2G, 3G] == [1L, 2L, 3L].stream().map(3G::valueOf).collect(toList())
}

methodRefs()
Groovy
@CompileStatic
void constructorRefs() {
    assert [1, 2, 3] == ['1', '2', '3'].stream().map(Integer::new).collect(toList())   // 使用类构造函数

    def result = [1, 2, 3].stream().toArray(Integer[]::new) // 使用数组构造函数
    assert result instanceof Integer[]
    assert result.toString() == '[1, 2, 3]'
}

constructorRefs()

7.正则表达式运算符

7.1.Pattern运算符

Groovy中使用~简化了创建java.util.regex.Pattern实例。

Groovy
def p = ~/foo/
assert p instanceof Pattern

~支持Groovy中的任意String类型:

Groovy
p = ~'foo'
p = ~"foo"
p = ~$/dollar/slashy $ string/$
p = ~"${pattern}"

7.2.查找运算符

除了~方式,还可以使用=~运算符直接创建一个java.util.regex.Matcher实例。

Groovy
def text = "some text to match"
def m = text =~ /match/ // 使用=~右侧的正则表达式创建匹配
assert m instanceof Matcher // 返回的类型是Matcher
if (!m) { // 等效于if (!m.find(0))
    throw new RuntimeException("Oops, text not found!")
}

7.3.匹配运算符

==~是Match.find的简单实用方式,它只返回布尔值。==~是严格匹配的。

Groovy
m = text ==~ /match/
assert m instanceof Boolean
if (m) {
    throw new RuntimeException("Should not reach that point!")
}

8.其他运算符

8.1.扩展运算符

扩展点运算符*.通常作用于集合对象。它会对集合的每一项都做同一运算并将结果保存在一个集合中。

Groovy
class Car {
    String make
    String model
}
def cars = [
       new Car(make: 'Peugeot', model: '508'),
       new Car(make: 'Renault', model: 'Clio')]
def makes = cars*.make //访问cars中的每一个元素的make
assert makes == ['Peugeot', 'Renault'] //将cars中的每个元素的make字段值添加到列表中。

这里的cars*.make等同于cars.collect{ it.make }。当要访问聊表中元素的属性时,Groovy的GPath表示法允许使用这样的快捷方式来访问,它会自动扩展到每一个元素。
在上面的例子中使用cars.make也可以使用,但是更推荐显式地保留*.运算符。

*.运算符是空安全的,对于空元素会返回null而不是空异常。

*.可以用于所有实现了Iterable的类。

Groovy
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']

当数据包含了多层结构时,使用多次*.调用来访问数据。

Groovy
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方法来获取所有元素的属性。

Groovy
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.扩展方法参数

当方法传参于列表的各项一一对应时,可以使用扩展方法参数的方式调用方法,而不需要从列表中逐个取出参数传入方法。

Groovy
int function(int x, int y, int z){
    x * y + z
}
def args = [4, 5, 6]
assert function(*args) == 26

另外可以将正常参数和扩展参数混合使用:

Groovy
args = [4]
assert function(*args, 5, 6) == 26

8.1.2.扩展列表

Groovy
def items = [4, 5]
def list = [1, 2, 3, *items, 6]
assert list == [1, 2, 3, 4, 5, 6]

8.1.3.扩展map

使用*:的方式将一个map内联到另一个map内。

Groovy
def m1 = [c:3, d:4]
def map = [a:1, b:2, *:m1]
assert map == [a:1, b:2, c:3, d:4]
Groovy
def m1 = [c:3, d:4]
def map = [a:1, b:2, *:m1, d: 8]
assert map == [a:1, b:2, c:3, d:8]

*:方式内联进来的元素支持被覆盖。

8.2.范围运算符

Groovy支持使用..来创建一个Range

Groovy
def range = 0..5 // 从0到5的列表
assert (0..5).collect() == [0, 1, 2, 3, 4, 5]
assert (0..<5).collect() == [0, 1, 2, 3, 4] // 从0到4的列表
assert (0..5) instanceof List
assert (0..5).size() == 6

任何可比较对象都可以使用..

8.3.Spaceship运算符

<=>是对compareTo方法的代理

Groovy
assert (1 <=> 1) == 0
assert (1 <=> 2) == -1
assert (2 <=> 1) == 1
assert ('a' <=> 'z') == -1

8.4.下标运算符

[]getAtputAt的简写,取决于在赋值符号的左边还是右边调用。

Groovy
def list = [0,1,2,3,4]
assert list[2] == 2
list[2] = 4
assert list[0..2] == [0,1,4]
list[0..2] = [6,6,6]
assert list == [6,6,6,3,4]

重写类的getAtputAt方法,可以使用下标来访问属性。

Groovy
class User {
    Long id
    String name
    def getAt(int i) {
        switch (i) {
            case 0: return id
            case 1: return name
        }
        throw new IllegalArgumentException("No such element $i")
    }
    void putAt(int i, def value) {
        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')
assert user[0] == 1
assert user[1] == 'Alex'
user[1] = 'Bob'
assert user.name == 'Bob'

8.5.安全索引运算符

Groovy3.0引入了安全索引运算符:?[], 和?.相似。

Groovy
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和列表的contais方法。

Groovy
def list = ['Grace','Rob','Emmy']
assert ('Emmy' in list)
assert list.isCase('Emmy')
assert list.contains('Emmy')

8.7.相等运算符

在Groovy使用==判断是否相等和Java中不同,使用==实际是调用了实例的equals方法。
如果需要判断是否为相同引用,应该使用is

Groovy
def list1 = ['Groovy 1.8','Groovy 2.0','Groovy 2.3']
def list2 = ['Groovy 1.8','Groovy 2.0','Groovy 2.3']
assert list1 == list2
assert !list1.is(list2)

8.8.强转运算符

强转运算符as是类型强转的变体,但不同于类型强转。

Groovy
Integer x = 123
String s = (String) x // Integer不能强转成String,会抛出ClassCastException
Groovy
Integer x = 123
String s = x as String
println s
assert "123" == s // 使用as成功把Integer转换成了String

当对象被强制转换为另一个对象时,除非目标类型与源类型相同,否则强制将返回一个新对象。强制规则因源类型和目标类型而异,如果未找到转换规则,则强制可能会失败。自定义转换规则可以由asType方法实现:

Groovy
class Identifiable {
    String name
}
class User {
    Long id
    String name
    def asType(Class target) {
        if (target == Identifiable) {
            return new Identifiable(name: name)
        }
        throw new ClassCastException("User cannot be coerced into $target")
    }
}
def u = new User(name: 'Xavier')
def p = u as Identifiable
assert p instanceof Identifiable
assert !(p instanceof User)

8.9.尖括号运算符

尖括号运算符<>是仅添加语法的糖运算符,以支持与Java 7中同名运算符的兼容性。它用于指示应从声明中推断出泛型类型:

Groovy
List<String> strings = new LinkedList<>()

在动态Groovy中,这是完全未使用的。 在静态类型检查的Groovy中,它也是可选的,因为Groovy类型检查器会执行类型推断,无论是否存在此运算符。

8.10.Call运算符

()运算符是隐式地调用call方法。对于任意定义了call方法的对象,可以省略.call的部分,而直接使用Call运算符。

Groovy
class MyCallable {
    int call(int x) { // 在MyCallable中定义了call方法
        2*x
    }
}

def mc = new MyCallable()
assert mc.call(2) == 4 // 使用传统的调用方式调用call方法
assert mc(2) == 4 // 省略.call直接使用()运算符

9.运算符优先级

下面列出了Groovy 操作符的优先级

Level Operator(s) Name(s)
1 new () object creation, explicit parentheses
() {} [] method call, closure, literal list/map
. .& .@ member access, method closure, field/attribute access
?. * *. *: safe dereferencing, spread, spread-dot, spread-map
~ ! (type) bitwise negate/pattern, not, typecast
[] ?[] ++ -- list/map/array (safe) index, post inc/decrement
2 ** power
3 ++ -- + - pre inc/decrement, unary plus, unary minus
4 * / % multiply, div, remainder
5 + - addition, subtraction
6 << >> >>> .. ..< left/right (unsigned) shift, inclusive/exclusive range
7 < <= > >= in !in instanceof !instanceof as less/greater than/or equal, in, not in, instanceof, not instanceof, type coercion
8 == != <=> === !== equals, not equals, compare to, identical to, not identical to
=~ ==~ regex find, regex match
9 & binary/bitwise and
10 ^ binary/bitwise xor
11 ` `
12 && logical and
13 `
14 ? : ternary conditional
?: elvis operator
15 = **= *= /= %= += -= <<= >>= >>>= &= ^= ` = ?=`

10.运算符重载

Groovy允许多种运算符的重载。

Groovy
class Bucket {
    int size

    Bucket(int size) { this.size = size }

    Bucket plus(Bucket other) { // Bucket实现了一个plus方法
        return new Bucket(this.size + other.size)
    }
}

def b1 = new Bucket(4)
def b2 = new Bucket(11)
assert (b1 + b2).size == 15 // 因为实现了plus方法,所以可以使用+运算符

下面是运算符及其对应方法的完整列表:

Operator Method Operator Method
+ 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.and(b) -- a.previous()
^ a.xor(b) +a a.positive()
as a.asType(b) -a a.negative()
a() a.call() ~a a.bitwiseNegate()

文章作者: suifeng
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 suifeng !