Groovy之操作符

ProjectDaedalus

共 8084字,需浏览 17分钟

 · 2022-05-15

这里对Groovy中常见的操作符进行介绍

abstract.png

操作符用法

算术操作符

同Java一样,在算术操作符方面Groovy并无二致。示例代码如下所示

class OperatorDemo {    
    static void arithmetic() {
        // 加
        assert 3+4 == 7
        // 加并赋值
        def foo1 = 3
        foo1 += 4
        assert foo1 == 7

        // 减
        assert 4-3 == 1
        // 减并赋值
        def foo2 = 4
        foo2 -= 3
        assert foo2 == 1

        // 乘
        assert 4*3 == 12
        // 乘并赋值
        def foo3 = 4
        foo3 *=3
        assert foo3 == 12

        // 除
        assert 12/3 == 4
        // 除并赋值
        def foo4 = 12
        foo4 /= 3
        assert foo4 ==4

        // 求余
        assert 13%3 == 1
        // 求余并赋值
        def foo5 = 13
        foo5 %= 3
        assert foo5 == 1

        // 幂
        assert 2**3 == 8
        // 幂并赋值
        def foo6 = 2
        foo6 **= 3
        assert foo6 == 8

        // 一元运算符 + 表示 正数
        assert +3 == 3
        // 一元运算符 - 表示 负数
        assert -4 == 0-4
        assert -(-11)  == 11

        // 后缀自增
        def a1 = 2
        def b1 = a1++
        assert a1 == 3
        assert b1 == 2

        // 前缀自增
        def a2 = 2
        def b2 = ++a2
        assert a2 == 3
        assert b2 == 3

        // 后缀自减
        def a3 = 2
        def b3 = a3--
        assert a3 == 1
        assert b3 == 2

        // 前缀自减
        def a4 = 2
        def b4 = --a4
        assert a4 == 1
        assert b4 == 1
    }
}   

关系运算符

Groovy的关系运算符示例如下所示

class OperatorDemo {    
    static void relational() {
        // 相等
        assert (3*4) == (10+2)

        // 不相等
        assert 3 != 4

        // 小于
        assert 3 < 4

        // 小于等于
        assert 3<=4
        assert 4<=4

        // 大于
        assert 5 > 4
        
        // 大于等于
        assert 5>=4
        assert 5>=5
    }
}

逻辑运算符

Groovy在逻辑运算符支持常见的与、或、非,同时具备短路求值的特点

class OperatorDemo {    
    static void logical() {
        // 逻辑与, 支持短路求值
        assert true && true
        
        // 逻辑或, 支持短路求值
        assert false || true
        
        // 逻辑非
        assert !false
    }
}

位运算

Groovy与Java一样,同样支持位运算。示例如下所示

class OperatorDemo {
    static void bit() {
        // 按位与
        int a = 0b1010  // a = 10
        assert a == 10
        int b = 0b0110  // b = 6
        assert b == 6
        int c = 0b0010  // c = 2
        assert c ==2
        assert (a&b) == c

        // 按位或
        int d = 0b1110  // d = 14
        assert d == 14
        assert (a|b) == d

        // 按位异或
        int e = 0b1100
        assert e == 12
        assert (a^b) == e

        // 按位取反
        byte f = 0b00001111
        assert f == 15
        byte h = 0b11110000
        assert h == -16
        assert (~f) == h

        // 左移
        byte i = 0b00000011
        assert i == 3
        byte j = 0b00001100
        assert j == 12
        assert (i<<2) == j

        // 右移
        assert (j>>2) == i
        // 右移: 左边使用原符号位进行填充, 右边超出部分直接丢弃
        byte k = 0b11111011
        assert k == -5
        byte p = 0b11111110
        assert p == -2
        assert (k>>2) == p

        // 无符号右移: 左边使用0进行填充, 右边超出部分直接丢弃
        int q = 0x8022_11ff // 数字支持使用下划线进行划分, 便于人眼查看
        assert q == -2145250817
        int r = 0x0080_2211
        assert r == 8397329
        assert (q>>>8) == r
    }
}

条件运算符

Groovy不仅提供了传统的三元运算符。还特别提供了Elvis运算符、Elvis赋值运算符。对于前者而言,如果Elvis运算符左边的操作数判定为真,则返回左边操作数; 否则返回右边的操作数;对于后者而言,Elvis赋值运算符, 其是对Elvis运算符的进一步简化, 省去了再次赋值操作。示例如下所示

class OperatorDemo {
    static void conditional() {
        // 三元运算符
        def a1 = (2>1) ? 3 : 4
        assert a1 == 3
        def a2 = (2>3) ? 3 : 4
        assert a2 == 4

        // Elvis 运算符, 如果?:运算符左边的操作数判定为真,则返回左边操作数; 否则返回右边的操作数
        // 可以视为简版的三元运算符
        def b1 = "Hello World"
        //  等同于 b1 = (b1!=null || b1!="") ? b1 : "Hi"
        b1 = b1 ?: "Hi" // 非空字符串视为真
        assert b1 == "Hello World"
        // Elvis 运算符
        def b2 = ""
        b2 = b2 ?: "Hi"
        assert b2 == "Hi"

        // Elvis赋值 运算符, 其是对Elvis运算符的进一步简化, 省去了再次赋值操作
        def c1 = "Hello World"
        c1 ?= "Hi"
        assert c1 == "Hello World"

        def c2 = ""
        c2 ?= "Hi"
        assert c2 == "Hi"
    }
}

正则操作符

特别地,Groovy针对正则操作提供了相应的操作符。示例如下所示

class OperatorDemo {
    static void regular() {
        String regex = /\S+\s+\S+/
        // 模式操作符 ~
        def pattern1 = ~regex
        assert pattern1 instanceof Pattern
        def text1 = "One Two Three Four Five"
        def matcher1 = pattern1.matcher(text1)
        assert matcher1 instanceof Matcher
        assert matcher1.size() == 2
        assert matcher1[0] == "One Two"
        assert matcher1[1] == "Three Four"

        // 查找运算符 =~
        // 具体地,其会在文本text1上应用正则表达式regex, 生成matcher
        def matcher2 = (text1 =~ regex)
        assert matcher2 instanceof Matcher
        assert matcher2.size() == 2
        assert matcher2[0] == "One Two"
        assert matcher2[1] == "Three Four"

        // 匹配运算符 ==~, 其是完整的匹配, 而非部分匹配
        boolean b1 = "One Two Three Four" ==~ regex
        boolean b2 = "One Two" ==~ regex
        assert b1 == false
        assert b2 == true
    }
}

对象操作符

Groovy对于对象引用提供了丰富的操作符。需要特别提醒的是,在Java中对两个对象引用使用==操作符比较的是两个对象的地址是否一样;而在Groovy中==操作符用于比较两个对象的内容是否一样,事实上该操作符是通过equals方法实现的。当然Groovy自然也是支持比较两个对象的地址,其提供了===操作符。事实上该操作符是通过is方法实现的。示例代码如下所示

class OperatorDemo 
    static void object() {
        def person1 = new Person("remark""领军人才")
        // 通过.操作符访问字段
        person1.name = "Aaron"
        assert person1.name == "Aaron"
        // 通过.操作符修改字段实际上是隐式调用setter方法
        person1.age = 18
        assert person1.age == 218
        // 通过.操作符获取字段实际上是隐式调用getter方法
        assert person1.remark == ": 领军人才"

        // 通过.@运算符可以实现直接访问字段, 而不是通过隐式调用getter、setter方法实现
        assert person1.@remark == "领军人才"
        person1.@age = 17
        assert person1.age == 17

        // 安全引用操作符
        Person person2 = null
        // 如果?.安全引用操作符的引用为null, 则不会调用方法, 而是直接返回null以避免NPE
        assert person2?.getAge() == null
        assert person1?.getAge() == 17

        // 方法指针运算符
        MethodClosure fun1 = person1.&getAge
        // 方法指针的类型是闭包
        assert fun1 instanceof Closure
        assert fun1() 
== 17
        assert fun1.call() == 17

        // 方法指针同样支持多分派
        def fun3 = person1.&test1
        assert fun3.call("Bye") == "Aaron: Bye"
        assert fun3.call(3) == "<17 + 3> -->> 20"

        // 可通过new获取构造器的方法指针
        def fun4 = Person.&new
        Person person3 = fun4.call("name":"Bob")
        assert person3.name == "Bob"

        // 通过类先获取非静态方法的方法指针
        def fun2 = String.&toUpperCase
        // 在执行闭包时, 再传入该类的实例, 以作为方法的调用者
        assert fun2.call("Hello") == "HELLO"
        def fun5 = Person.&test1
        assert fun5.call( person1, "welcome" ) == "Aaron: welcome"

        // 方法指针运算符同样适用于静态方法
        def fun6 = String.&valueOf
        assert fun6.call( false ) == "false"
        assert fun6.call( 996 ) == "996"

        // Groovy对Java 8的::方法引用运算符保持支持兼容
        def list1 = ["71","2","4"].stream()
            .map( Integer::valueOf )
            .collect( Collectors.toList() )
        assert list1 == [71,2,4]

        // list1a, list1b 引用地址相同
        def list1a = [1,2] as LinkedList
        def list1b = list1a
        // 另外一个包含相同元素的列表
        def list2 = [1,2] as LinkedList

        // 判断两个引用的内容是否相同
        // == 运算符所对应的方法是equals
        assert list1a == list2
        assert list1a.equals( list2 )

        // 类似地, !=运算符是对equals方法的结果进行否定
        assert list1a != [985,211]
        assert !( list1a.equals([985,211]) )

        // 判断两个引用的地址是否相同
        // ===运算符对应is方法
        assert list1a === list1a
        assert list1a.is(list1b)
        // 判断两个对象的引用地址是否不同
        // 类似地, !==运算符是对is方法的结果进行否定
        assert list1a !== list2
        assert !( list1a.is(list2) )
    }
}

class Person {
    String name
    Integer age
    String remark

    void setAge(Integer age) 
{
        this.age = 200 + age
    }

    String getRemark() {
        return ": $remark"
    }

    /**
     * 实现equals方法, 实现重载==运算符
     * @param other
     * @return
     */

    @Override
    boolean equals(Object other) {
        if (!other
            || !(other instanceof Person)
            || name != other.name
            || age != other.age
            || remark != other.remark ) {
            return  false
        }
        return true
    }

    String test1(String msg) {
        return "$name: $msg"
    }

    String test1(Integer num) {
        return "<${this.age} + ${num}> -->> ${this.age+num}"
    }
}

其它

Groovy中其它部分常见的操作符,示例如下所示

class OperatorDemo {
    static void other() {
        // 飞船运算符, 通过调用Comparable接口的compareTo方法进行比较
        // 15==15
        assert (15 <=> 15) == 0
        // 44>22
        assert (44 <=> 22) == 1
        // 22<44
        assert (22 <=> 44) == -1

        // 安全索引运算符?[], 作用类似于?.安全引用操作符
        // 避免由于数组为null而导致的NPE
        String[] array1 = ["Amy""Aaron"]
        array1?[1] = "Bob"
        assert array1?[0] == "Amy"
        assert array1?[1] == "Bob"
        array1 = null
        // array1为null, 将不会应用索而是直接返回null
        assert array1?[0] == null

        // 安全索引运算符同样适用于Map
        def map1 = [:]
        map1?["Aaron"] = 18
        assert map1?["Aaron"] == 18
        map1 = null
        assert map1?["Aaron"] == null

        // 成员操作符
        def list1 = ["MicroSoft""Apple""Xiaomi""FaceBook"]
        // 判定Apple是否是list1的成员
        assert "Apple" in list1
        // 等效于调用isCase方法
        assert list1.isCase( "Apple" )
        assert !("Huawei" in list1)

        assert 996 in Integer
        // 等效于调用isCase方法
        assert Integer.isCase(996)
        assert !(3.14f in Integer)

        assert 3.14f in Float
        assert Float.isCase( 3.1f )
    }
}

操作符重载

在Groovy中,部分操作符是有对应的方法。换言之,通过操作符或方法调用在本质上效果是一致的。但操作符一旦有对应的方法,就为我们提供了另外一种编程方式,即进行操作符的重载。在特殊场景下,操作符的重载可以大大方便我们的使用。比如期望通过乘法符号计算两个矩阵的乘积,在Java中这显然是不可能。因为我们不能自定义乘法操作符的具体逻辑,而在Groovy中则可以在我们自定义的矩阵类中通过重载乘法操作符实现。这里给出Groovy可以进行重载的操作符及对应的方法名

figure 1.jpeg

下面给出一个进行重载操作符的示例,方便理解、使用

class OperatorOverLoad {
    static void main(String[] args){
        testPlus()
        testNext()
        testCall()
    }
    
    static void testNext() {
        Food food1 = new Food("西瓜"14)
        food1++
        assert food1.toString() == "Food { type=西瓜, num=15 }"

        def food2 = new Food(type: "哈蜜瓜" )
        food2++
        assert food2.toString() == "Food { type=哈蜜瓜, num=1 }"
    }

    static void testPlus() {
        Food food1 = new Food("橘子"1)
        Food food2 = new Food("橘子"2)
        Food food3 = new Food("type""橘子")
        Food food4 = new Food("type""苹果")

        assert (food1 + food2).toString() == "Food { type=橘子, num=3 }"
        assert (food2 + food3).toString() == "Food { type=橘子, num=2 }"
        assert (food1 + food4) == null
    }

    static void testCall() {
        Food food1 = new Food("柠檬"21)
        def str = food1()
        // 显式调用call方法
        assert food1.call() == "Food { type=柠檬, num=21 }"
        // 通过 调用运算符() 调用 call方法
        assert food1() == "Food { type=柠檬, num=21 }"
    }
}

class Food {
    String type
    Integer num

    Food(String type, Integer num) {
        this.type = type
        this.num = num
    }

    Food() {
    }

    /**
     * 重载加法运算符+
     * @param other
     * @return
     */

    Food plus(Food other) {
        if( !this.type || this.type != other?.type ) {
            return null
        }

        Integer num1 = this.num ?: 0
        Integer num2 = other.num ?: 0
        Integer result = num1 + num2
        return new Food("type":type, "num": result)
    }

    /**
     * 重载自增运算符++
     * @return
     */

    Food next() {
        this.num ?= 0
        this.num++
        return this
    }

    /**
     * 重载调用运算符()
     * @return
     */
    String call() {
        return toString()
    }

    @Override
    String toString() {
        return "Food { type=$type, num=$num }"
    }
}

参考文献

  1. Groovy In Action · 2nd Edition   Dierk König、Guillaume Laforge著
浏览 13
点赞
评论
收藏
分享

手机扫一扫分享

举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

举报