Unmi 学习 Groovy 之操作符重载

Java 相比于 C++ 语法来说去除了指针及显式内存释放,受到不少赞誉,的确避免很多的出错的可能性,提高了生产率。可是把操作符重载也拿掉了,也没了条件编译。这两项特性在 C# 是有的。操作符在 C++ 中似乎不是很好理解,它可以带来很大的灵活性,和操作的直观性。Java 大约想的是过宽的灵活性怕带来过度的滥用,在大型项目会有所不利。

因此由 Java 所衍生的 Groovy 脚本像 Perl、Python、Ruby 一样又有了操作符重载,脚本基于其应用领域需要更多的灵活性和自由度。

Groovy 中对操作的操作比起 C++ 中来说更简单,Groovy 中是把操作符映射到对应命名方法的调用,你只要重载了该命名方法便是重载了相应的操作符,比如说加号+ 映射到 plus(obj) 方法,你只要重载了 plus(obj) 即改变了加号+ 的形为。对于其他符号也是一样的。

举例说明,例如有一个 Basket,装着白菜和萝卜的数量分别为 cabbageAmt, radishAmt,当两个 Basket 中东西要摆放到一个大 Basket 里并返回一个新的 Basket 实例时,就要对先前两个篮子里的白菜和萝卜数量分别相加,这时候就是要重载 Basket 的 plus 方法,代码如下:

输出加操后的结果: In that basket, the amount of cabbage is 4 and the amount of radish is 6

对于其他的操作符的重载,要重载的相应命名方法的对应表如下,也是 Groovy 语法目前所支持的所有操作符:

Operator Name Method Works with
a + b Plus a.plus(b) Number,string,collection
a - b Minus a.minus(b) Number,string,collection
a * b Star a.multiply(b) Number,string,collection
a / b Divide a.div(b) Number
a % b Modulo a.mod(b) Integral number
a++ Post increment a.next() Number,string,range
++a Pre increment
a-- Post decrement a.previous() Number,string,range
--a Pre decrement
a**b Power a.power(b) Number
a | b Numerical or a.or(b) Integral number
a & b Numerical and a.and(b) Integral number
a ^ b Numerical xor a.xor(b) Integral number
~a Bitwise complement a.negate() Integral number,string
(the latter returning a regular expression pattern)
a[b] Subscript a.getAt(b) Object,list,map,string、Array
a[b] = c Subscript assignment a.puAt(b,c) Object,list,map,StringBuffer,
Array
a << b Left shift a.leftShift(b) Integral number, also used like "append" to StringBuffers, Writers, Files, Sockets, Lists
a >> b Right shift a.rightShift(b) Integral number
a >>> b Right shift unsigned a.rightShiftUnsigned(b) Integral number
switch(a){
case b:
}
Classification b.isCase(a) Object, range, list, collection, pattern, closure; also used with collection c in c.grep(b), which returns all items of c where b.isCase(item)
a == b Equals a.equals(b) Object, consider hashCode()
a != b Not equals !a.equals(b) Object
a <=> b Spaceship a.compareTo(b) java.lang.Comparable
a > b Greater than a.compareTo(b) > 0
a >= b Greater than or equal to a.compareTo(b) >= 0
a < b Less than a.compareTo(b) < 0
a <= b Less than or equal to a.compareTo(b) <= 0
a as type Enforced coercion a.asType(typeClass) Any type

重载 equals() 方法时,Java 强烈建议重载 hashCode() 方法,对象相等时希望 hashcode 也是一样,尽管是非必要的,参看 java.lang.Object#equals 的 API 说明。

严格意义上讲,还有更多的符号可以重载,例如,引用字段和方法的点(.) 操作符就可以被重载了。通过对 Date 的 . 操作重载可以支持以下代码的写法(这要用到 MetaClass 来改造原有类),这要再来一篇细究的:

newDate = date + 1.month + 3.days + 5.hours; //IBM DB2 数据库就支持这种更人性化的写法

还应注意以下几个情况:

1) 在 Groovy 中的 a==b 比较操作是依照 equals() 方法来比较的。而在 Java 的 a==b 比较操作,到了 Groovy 中就要用 a.is(b)

2) 有些操作已经在许多的 Java 类或是 Groovy 数据结构中实现了,那么就可以直接使用,例如对两个 Double 类型可以用 ==> 等符号进行比较;
还有 Groovy 中的集合,如

3) Groovy 在使用比较操作符时对 null 值处理很优雅。换句话说,如果一个(或者两个) 值为 null,就不会抛出 java.lang.NullPointerException

比如再给上面的 Basket 加上方法

再执行以下代码:

执行后的输出为 false,如果按照 Java 中的思维, basket1 ==basket2 会调用 Basket 的 equals() 方法,而 basket2 为 null,在执行 equals() 方法时就会抛出 java.lang.NullPointerException 异常,而实质上是 Groovy 在生成 equals() 方法时插入了 null 检查代码,所以不抛出异常。

4) 因为被重载的符号是映射到相应的方法,所以 basket1 + basket2 实际和 basket1.plus(basket2) 是一致的,即调用第一个操作数的 plus() 方法,第二个操作数为参数。这里前后两个操作数是同类型的,所以应用加号的交互法则,写成 basket2 + basket1 也成立,因为对 baksket2.plus(basket1) 调用是合法的。

不过,要是在 Basket 类中定义如下方法:(加上一个整数,我们假定给篮子加 1 的意义在于白菜和萝卜的数量各加上 1)

那么我们写下这样的代码

basket1 + 1;

OK,没问题,如果还想当然的可以互换,写成 1 + basket1; 那就有异常了,

groovy.lang.MissingMethodException: No signature of method: java.lang.Integer.plus() is applicable for argument types: (Basket)

因为这时候是通过 Integer 来调用 plus(bakset) 的,即 1.plus(basket1),而 Integer 是没有定义 plus(Basket basket) 这样一个方法,所以出错。如果两边都定义了相应的 plus() 方法,就可以把加数和被加数互换了。这个问题在其他有符号重载特性的语言中同样是存在的,只要留意了被重载的方法内部实际是如何被调用的就不会有问题了。
参考:1. 《Java 脚本编程语言、框架与模式》第 4 章
2. 《Groovy in Action》2.2 Overriding operators

本文链接 https://yanbin.blog/unmi-study-groovy-operator-overload/, 来自 隔叶黄莺 Yanbin Blog

[版权声明] Creative Commons License 本文采用 署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0) 进行许可。

Subscribe
Notify of
guest

5 Comments
Inline Feedbacks
View all comments
prisoner
prisoner
15 years ago

妙哉。。。

试客网
15 years ago

叫好,喜欢~

Johnny
15 years ago

“而在 Java 的 a==b 比较操作,到了 Groovy 中就要写成 a === b,JavaScript 中也是这么用的。”

Groovy已经没有===操作符了,比较是否是同一实例要用a.is(b)

Johnny
15 years ago

Groovy这种运算符重载是所有语言里面我最喜欢的,即使要记住这些函数名……

隔叶黄莺
15 years ago

@Johnny

谢谢提醒,是的,我在 Groovy 1.5.6 中试了确实没有 === 操作了。

那么这个 is(Object obj) 就是每一个 Groovy 对象都有的方法,看来那本 《Scrpting in Java》这本书有点老。

还是自己不够严谨,没实际测试一下这个操作就写上来了。

原文改正过来了。

我又到网上下了 Groovy 1.0,试了下,也没有 === 这个操作,看来这个操作只在 0.x.x 版的 Groovy 中出现过。