
Java™ 取消了 *** 作符重载,但是新兴的 Groovy 又使之浮出水面。在 实战 Groovy 定期连载的“Groovy 每日应用”的最后一期中,请随着 Andrew glover 介绍的三类可重载 *** 作符,重新寻回自己多年来失去的东西。
许多以前使用 C++ 的开发人员会怀念 *** 作符重载,例如 + 和 -。虽然它们很方便,但是被覆盖的 *** 作符的多态实质会造成混淆,所以 *** 作符重载在 Java 语言中被取消了。这个限制的好处是清晰:Java 开发人员不必猜想两个对象上的 + 是把它们加在一起还是把一个对象附加到另一个对象上。不好的地方则是丧失了一个有价值的简写形式。
|
现在,期望放任自由的 Groovy 把这个简写形式带回来!在 实战 Groovy 的这一期中,我将介绍 Groovy 对 *** 作符即时多态(也称为 *** 作符重载)的支持。正如 C++ 开发人员会告诉您的,这个东西既方便又有趣,虽然必须小心谨慎才能接近。
三类可重载 *** 作符
我把 Groovy 的可重载 *** 作符分成三个逻辑组: 比较 *** 作符、算术类 *** 作符和数组类 *** 作符。这几组只涵盖了普通 Java 编程中可用 *** 作符的一个子集。例如,像 & 和 ^ 这样的逻辑 *** 作符目前在 Groovy 中不可用。
表 1 显示了 Groovy 中可用的三组可重载 *** 作符:
表 1. Groovy 的可重载 *** 作符
| 1. | 比较 *** 作符对应着普通的 Java equals 和 compareto 实现 |
| 2. | Java 的算术类 *** 作符,例如 +、- 和 * |
| 3. | 数组存取类 *** 作符 [] |
@H_404_104@
比较 *** 作符
比较 *** 作符对应着 Java 语言中的 equals 和 compareto 实现,通常用作集合中排序的快捷方式。表 2 显示了 Groovy 的两个比较 *** 作符:
表 2. 比较 *** 作符
| *** 作符 | 方法 |
|---|---|
| a == b | a.equals(b) |
| a <=> b | a.compareto(b) |
*** 作符 == 是 Java 语言中表示对象相等的简写形式,但是不代表对象引用的相等。换句话说,放在两个对象之间的 Groovy 的 == 意味着它们相同,因为它们的属性是相等的,虽然每个对象指向独立的引用。
根据 Javadoc,Java 语言的 compareto() 方法返回负整数、0 或正整数,代表对象小于、等于或大于指定对象。因为这个方法能够返回三个值,所以 Groovy 用四个附加值扩充了 <=> 语法,如表 3 所示:
表 3. 四个附加的值
| *** 作符 | 含义 |
|---|---|
| a > b | 如果 a.compareto(b) 返回的值大于 0,那么这个条件为 true |
| a >= b | 如果 a.compareto(b) 返回的值大于等于 0,那么这个条件为 true |
| a < b | 如果 a.compareto(b) 小于 0,那么这个条件为 true |
| a <= b | 如果 a.compareto(b) 小于等于 0,那么这个条件为 true |
比较购物
还记得我最初在“感受 Groovy”一文中定义的磁盘友好的 LavaLamp 类么,它还在“实战 Groovy: Groovy 的腾飞”一文中充当迁移到新的 JsR 语法的示例,我要再次使用这个类来演示使用比较 *** 作符的一些漂亮的技巧。
在清单 1 中,我通过实现普通 Java 的 equals() 方法(以及它的可恶伙伴 hashCode)增强了 LavaLamp 类。另外,我让 LavaLamp 实现了 Java 语言的 Comparable 接口并创建了 compareto() 方法的实现:
清单 1. LavaLamp 归来!
@H_404_104@
package com.vanward.groovy
import org.apache.commons.lang.builder.ComparetoBuilder
import org.apache.commons.lang.builder.EqualsBuilder
import org.apache.commons.lang.builder.HashCodeBuilder
import org.apache.commons.lang.builder.ToStringBuilder
class LavaLamp implements Comparable{
@Property model
@Property basecolor
@Property liquIDcolor
@Property lavacolor
def String toString() {
return new ToStringBuilder(this).
append(this.model).
append(this.basecolor).
append(this.liquIDcolor).
append(this.lavacolor).
toString()
}
def boolean equals(obj) {
if (!(obj instanceof LavaLamp)) {
return false
}
LavaLamp rhs = (LavaLamp) obj
return new EqualsBuilder().
append(this.model,rhs.model).
append(this.basecolor,rhs.basecolor).
append(this.liquIDcolor,rhs.liquIDcolor).
append(this.lavacolor,rhs.lavacolor).
isEquals()
}
def int hashCode() {
return new HashCodeBuilder(17,37).
append(this.model).
append(this.basecolor).
append(this.liquIDcolor).
append(this.lavacolor).
toHashCode()
}
def int compareto(obj) {
LavaLamp lmp = (LavaLamp)obj
return new ComparetoBuilder().
append(lmp.model,this.model).
append(lmp.lavacolor,this.lavacolor).
append(lmp.basecolor,this.basecolor).
append(lmp.liquIDcolor,this.liquIDcolor).
toComparison()
}
}
注: 因为我是重用狂,所以我的这些实现严重依赖 Jakarta 的 Commons Lang 项目(甚至 toString() 方法也是如此!)如果您还在自己编写 equals() 方法,那么您可能想花一分钟来签出这个库。(请参阅 参考资料 。)
在清单 2 中,可以看到我在清单 1 中设置的 *** 作符重载的效果。我创建了五个 LavaLamp 实例(没错,我们在开 party!)并用 Groovy 的比较 *** 作符来区分它们:
清单 2. 比较 *** 作符的效果
@H_404_104@
lamp1 = new LavaLamp(model:"1341",basecolor:"Black",
liquIDcolor:"Clear",lavacolor:"Red")
lamp2 = new LavaLamp(model:"1341",basecolor:"Blue",lavacolor:"Red")
lamp3 = new LavaLamp(model:"1341",lavacolor:"Blue")
lamp4 = new LavaLamp(model:"1342",lavacolor:"DarkGreen")
lamp5 = new LavaLamp(model:"1342",lavacolor:"DarkGreen")
println lamp1 <=> lamp2 // 1
println lamp1 <=> lamp3 // -1
println lamp1 < lamp3 // true
println lamp4 <=> lamp5 // 0
assert lamp4 == lamp5
assert lamp3 != lamp4
注意 lamp4 和 lamp5 是如何相同的,以及其他对象之间具有什么样的细微差异。因为 lamp1 的 basecolor 是 Black 而lamp2 的 basecolor 是 Blue,所以 <=> 返回 1(black 中的 a 比 blue 中的 u 靠前)。类似地,lamp3 的 lavacolor 是 Blue,而 lamp1 的是 Red。因为条件 lamp1 <=> lamp3 返回 -1,所以语句 lamp1 < lamp3 返回 true。llamp4 和 llamp5 是相等的,所以 <=> 返回 0。
而且,可以看到 == 也适用于对象相等。lamp4 和 lamp5 是同一对象。当然,我应当用 assert lamp4.equals(lamp5) 证实这一点,但是 == 更快捷!
我想要我的引用相等
现在,如果真的想测试对象引用相等该怎么办?显然,我不能用 ==,但是 Groovy 为这类事情提供了 is() 方法,如清单 3 所示:
清单 3. is() 的作用!
@H_404_104@
lamp6 = new LavaLamp(model:"1344",
liquIDcolor:"Clear",lavacolor:"Purple")
lamp7 = lamp6
assert lamp7.is(lamp6)
在清单 3 中可以看出,lamp6 和 lamp7 是相同的引用,所以 is 返回 true。
顺便说一句,如果感觉迷糊,不要惊讶:使用重载 *** 作符差不多让 Groovy 语言退步了。但是我认为是有趣的。
@H_404_104@
算术类 *** 作符
Groovy 支持以下算术类 *** 作符的重载:
表 3. Groovy 的算术类 *** 作符
| *** 作符 | 方法 |
|---|---|
| a + b | a.plus(b) |
| a - b | a.minus(b) |
| a * b | a.multiply(b) |
| a / b | a.divIDe(b) |
| a++ or ++a | a.next() |
| a-- or --a | a.prevIoUs() |
| a << b | a.leftShift(b) |
您可能已经注意到 Groovy 中的 + *** 作符已经在几个不同的领域重载了,特别是在用于集合的时候。您是否想过,这怎么可能?或者至少想过对自己的类能否这么做?现在我们来看答案。
添加 *** 作符
还记得“在 Java 应用程序中加一些 Groovy 进来”一文中的 Song 类么?我们来看看当我创建一个 JukeBox 对象来播放 Song 时,发生了什么。在清单 4 中,我将忽略实际播放 MP3 的细节,把重点放在从 JukeBox 添加和减去 Song 上:
清单 4. Jukebox
@H_404_104@
package com.vanward.groovy
import com.vanward.groovy.song
class JukeBox {
def songs
JukeBox(){
songs = []
}
def plus(song){
this.songs << song
}
def minus(song){
def val = this.songs.lastIndexOf(song)
this.songs.remove(val)
}
def printPlayList(){
songs.each{ song -> println "${song.getTitle()}" }
}
}
通过实现 plus() 和 minus() 方法,我重载了 + 和 -,现在可以把它们用在脚本中了。清单 5 演示了它们从播放列表添加和减少歌曲的行为:
清单 5. 播放音乐
@H_404_104@
sng1 = new Song("Spanisheyes.mp3")
sng2 = new Song("RaceWithDevilSpanishHighway.mp3")
sng3 = new Song("Nena.mp3")
jBox = new JukeBox()
jBox + sng1
jBox + sng2
jBox + sng3
jBox.printPlayList() //prints Spanish Eyes,Race with the Devil..,Nena
jBox - sng2
jBox.printPlayList() //prints Spanish Eyes,Nena
重载,重载的,重载器
继续进行这个重载的 主题,您可能注意到,在表 3 中有一个可以重载的算术类 *** 作符 <<,它恰好也为 Groovy 的集合重载。在集合的情况下,<< 覆盖后的作用像普通的 Java add() 方法一样,把值添加到集合的尾部(这与 Ruby 也很相似)。在清单 6 中,可以看到当我模拟这个行为,允许 JukeBox 的用户通过 << *** 作符以及 + *** 作符添加 Song 时发生的情况:
清单 6. 左移音乐
@H_404_104@
def leftShift(song){
this.plus(song)
}
在清单 6 中,我实现了 leftShift 方法,它调用 plus 方法添加 Song 到播放列表。清单 7 显示了 << *** 作符的效果:
清单 7. 比赛进行中
@H_404_104@
jBox << sng2 //re-adds Race with the Devil...
可以看出,Groovy 的算术类重载 *** 作符不仅能负重,而且能做得很快!
@H_404_104@
数组类 *** 作符
Groovy 支持重载标准的 Java 数组存取语法 [],如表 4 所示:
表 4. 数组 *** 作符
| *** 作符 | 方法 |
|---|---|
| a[b] | a.getAt(b) |
| a[b] = c | a.putAt(b,c) |
数组存取语法很好地映射到集合,所以我在清单 8 中更新了 JukeBox 类,把两种情况都做了重载:
清单 8. Music 重载
@H_404_104@
def getAt(position){
return songs[position]
}
def putAt(position,song){
songs[position] = song
}
现在我实现了 getAt 和 putAt,我可以使用 [] 语法了,如清单 9 所示:
清单 9. 还能比这更快么?
@H_404_104@
println jBox[0] //prints Spanish Eyes
jBox[0] = sng2 //placed Race w/the Devil in first slot
println jBox[0] //prints Race w/the Devil
@H_404_104@
更 Groovy 化的 JDK 方法
一旦掌握了 *** 作符重载的概念和它在 Groovy 中的实现,就可以看到许多日常的 Java 对象已经 被 Groovy 的作者做了改进。
例如,Character 类支持 compareto(),如清单 10 所示:
清单 10. 比较字符
@H_404_104@
def a = Character.valueOf('a' as char)
def b = Character.valueOf('b' as char)
def c = Character.valueOf('c' as char)
def g = Character.valueOf('g' as char)
println a < b //prints true
println g < c //prints false
同样,StringBuffer 可以用 << *** 作符进行添加,如清单 11 所示:
清单 11. 缓冲区中的字符串
@H_404_104@
def strbuf = new StringBuffer()
strbuf.append("Error message: ")
strbuf << "NullPointerException on line ..."
println strbuf.toString() //prints Error message: NullPointerException on line ...
最后,清单 12 表示 Date 可以通过 + 和 - 来 *** 纵。
清单 12. 是哪一天?
@H_404_104@
def today = new Date()
println today //prints Tue Oct 11 21:15:21 EDT 2005
println "tomorrow: " + (today + 1) //Wed Oct 12 21:15:21 EDT 2005
println "yesterday: " + (today - 1) //Mon Oct 10 21:15:21 EDT 2005
@H_404_104@
结束语
可 以看到, *** 作符的即时多态,或 *** 作符重载,对于我们来说,如果小心使用和记录,会非常强大。但是,要当心不要滥用这个特性。如果决定覆盖一个 *** 作符去做一 些非常规的事情,请一定要清楚地记录下您的工作。对 Groovy 类进行改进,支持重载非常简单。小心应对并记录所做的工作,对于由此而来的方便的简写形式来说,代价非常公道。
总结以上是内存溢出为你收集整理的美妙的 *** 作符全部内容,希望文章能够帮你解决美妙的 *** 作符所遇到的程序开发问题。
如果觉得内存溢出网站内容还不错,欢迎将内存溢出网站推荐给程序员好友。
欢迎分享,转载请注明来源:内存溢出
微信扫一扫
支付宝扫一扫
评论列表(0条)