Groovy之操作符
这里对Groovy中常见的操作符进行介绍
操作符用法
算术操作符
同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可以进行重载的操作符及对应的方法名
下面给出一个进行重载操作符的示例,方便理解、使用
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 }"
}
}
参考文献
Groovy In Action · 2nd Edition Dierk König、Guillaume Laforge著