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 方法,代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
class Basket{ int cabbageAmt,radishAmt; def plus(Basket anotherBasket){ //两个 Basket 的加操作会调用这个方法 return new Basket(cabbageAmt:this.cabbageAmt+anotherBasket.cabbageAmt, radishAmt:this.radishAmt+anotherBasket.radishAmt); } String toString(){ return "In that basket, the amount of cabbage is " +cabbageAmt+" and the amount of radish is "+radishAmt; } } Basket basket1 = new Basket(cabbageAmt:1,radishAmt:2); Basket basket2 = new Basket(cabbageAmt:3,radishAmt:4); Basket basket3 = basket1 + basket2; println basket3; |
输出加操后的结果: 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 中的集合,如
1 2 |
list = [1,2,3] println list[2] //Groovy 为集合实现了的索引操作符[] |
3) Groovy 在使用比较操作符时对 null 值处理很优雅。换句话说,如果一个(或者两个) 值为 null,就不会抛出 java.lang.NullPointerException。
比如再给上面的 Basket 加上方法
1 2 3 4 |
def equals(Basket anotherBasket){ return this.cabbageAmt==anotherBasket.cabbageAmt && this.radishAmt==anotherBasket.radishAmt } |
再执行以下代码:
1 2 3 4 |
Basket basket1 = new Basket(cabbageAmt:1,radishAmt:2); Basket basket2 = null; println basket1 == basket2; |
执行后的输出为 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)
1 2 3 4 |
def plus(int i){ return new Basket(cabbageAmt:this.cabbageAmt+i, radishAmt:this.radishAmt+i); } |
那么我们写下这样的代码
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
[版权声明] 本文采用 署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0) 进行许可。
妙哉。。。
叫好,喜欢~
“而在 Java 的 a==b 比较操作,到了 Groovy 中就要写成 a === b,JavaScript 中也是这么用的。”
Groovy已经没有===操作符了,比较是否是同一实例要用a.is(b)
Groovy这种运算符重载是所有语言里面我最喜欢的,即使要记住这些函数名……
@Johnny
谢谢提醒,是的,我在 Groovy 1.5.6 中试了确实没有 === 操作了。
那么这个 is(Object obj) 就是每一个 Groovy 对象都有的方法,看来那本 《Scrpting in Java》这本书有点老。
还是自己不够严谨,没实际测试一下这个操作就写上来了。
原文改正过来了。
我又到网上下了 Groovy 1.0,试了下,也没有 === 这个操作,看来这个操作只在 0.x.x 版的 Groovy 中出现过。