服务热线:13616026886

技术文档 欢迎使用技术文档,我们为你提供从新手到专业开发者的所有资源,你也可以通过它日益精进

位置:首页 > 技术文档 > JAVA > 新手入门 > 开发工具 > 查看文档

groovy允许在java 平台上重载操作符

  java? 取消了操作符重载,但是新兴的 groovy 又使之浮出水面。在实战 groovy 定期连载的“groovy 每日应用”的最后一期中,请随着 andrew glover 介绍的三类可重载操作符,重新寻回自己多年来失去的东西。
  
  许多以前使用 c++ 的开发人员会怀念操作符重载,例如 + 和 -。虽然它们很方便,但是被覆盖的操作符的多态实质会造成混淆,所以操作符重载在 java 语言中被取消了。这个限制的好处是清晰:java 开发人员不必猜想两个对象上的 + 是把它们加在一起还是把一个对象附加到另一个对象上。不好的地方则是丧失了一个有价值的简写形式。
  
  把任何一个工具用于开发实践的关键是,知道什么时候使用它而什么时候把它留在工具箱中。脚本语言(或动态语言)可以是工具箱中极为强大的工具,但是只有在恰当地应用到适当的场景中才是这样。为了这个目标,实战 groovy 是一系列文章,专门介绍 groovy 的实际应用,并教给您什么时候、如何成功地应用它们。
  
  现在,期望放任自由的 groovy 把这个简写形式带回来!在 实战 groovy 的这一期中,我将介绍 groovy 对操作符即时多态(也称为操作符重载)的支持。正如 c++ 开发人员会告诉您的,这个东西既方便又有趣,虽然必须小心谨慎才能接近。
  
  三类可重载操作符
  
  我把 groovy 的可重载操作符分成三个逻辑组: 比较操作符、算术类操作符和数组类操作符。这几组只涵盖了普通 java 编程中可用操作符的一个子集。例如,像 & 和 ^ 这样的逻辑操作符目前在 groovy 中不可用。
  
  表 1 显示了 groovy 中可用的三组可重载操作符:
  
  表 1. groovy 的可重载操作符
  
  1. 比较操作符对应着普通的 java equals 和 compareto 实现
  2. java 的算术类操作符,例如 +、- 和 *
  3. 数组存取类操作符 []
  
  比较操作符
  
  比较操作符对应着 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 归来!
  
  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. 比较操作符的效果
  
  lamp1 = new lavalamp(model:"1341", basecolor:"black",
  liquidcolor:"clear", lavacolor:"red")
  lamp2 = new lavalamp(model:"1341", basecolor:"blue",
  liquidcolor:"clear", lavacolor:"red")
  lamp3 = new lavalamp(model:"1341", basecolor:"black",
  liquidcolor:"clear", lavacolor:"blue")
  lamp4 = new lavalamp(model:"1342", basecolor:"blue",
  liquidcolor:"clear", lavacolor:"darkgreen")
  lamp5 = new lavalamp(model:"1342", basecolor:"blue",
  liquidcolor:"clear", 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() 的作用!
  
  lamp6 = new lavalamp(model:"1344", basecolor:"black",
  liquidcolor:"clear", lavacolor:"purple")
  
  lamp7 = lamp6
  assert lamp7.is(lamp6)
  
  在清单 3 中可以看出,lamp6 和 lamp7 是相同的引用,所以 is 返回 true。
  
  顺便说一句,如果感觉迷糊,不要惊讶:使用重载操作符差不多让 groovy 语言退步了。但是我认为是有趣的。
  
  算术类操作符
  
  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
  
  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. 播放音乐
  
  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. 左移音乐
  
  def leftshift(song){
  this.plus(song)
  }
  
  在清单 6 中,我实现了 leftshift 方法,它调用 plus 方法添加 song 到播放列表。清单 7 显示了 << 操作符的效果:
  
  清单 7. 比赛进行中
  
  jbox << sng2 //re-adds race with the devil...
  
  可以看出,groovy 的算术类重载操作符不仅能负重,而且能做得很快!
  
  数组类操作符
  
  groovy 支持重载标准的 java 数组存取语法 [],如表 4 所示:
  
  表 4. 数组操作符
  
  操作符   方法
  a[b]    a.getat(b)
  a[b] = c  a.putat(b, c)
  
  数组存取语法很好地映射到集合,所以我在清单 8 中更新了 jukebox 类,把两种情况都做了重载:
  
  清单 8. music 重载
  
  def getat(position){
  return songs[position]
  }
  
  def putat(position, song){
  songs[position] = song
  }
  
  现在我实现了 getat 和 putat,我可以使用 [] 语法了,如清单 9 所示:
  
  清单 9. 还能比这更快么?
  
  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
  
  更 groovy 化的 jdk 方法
  
  一旦掌握了操作符重载的概念和它在 groovy 中的实现,就可以看到许多日常的 java 对象已经 被 groovy 的作者做了改进。
  
  例如,character 类支持 compareto(),如清单 10 所示:
  
  清单 10. 比较字符
  
  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. 缓冲区中的字符串
  
  def strbuf = new stringbuffer()
  strbuf.append("error message: ")
  strbuf << "nullpointerexception on line ..."
  
  println strbuf.tostring() //prints error message: nullpointerexception on line ...
  
  最后,清单 12 表示 date 可以通过 + 和 - 来操纵。
  
  清单 12. 是哪一天?
  
  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
  
  结束语
  
  可以看到,操作符的即时多态,或操作符重载,对于我们来说,如果小心使用和记录,会非常强大。但是,要当心不要滥用这个特性。如果决定覆盖一个操作符去做一些非常规的事情,请一定要清楚地记录下您的工作。对 groovy 类进行改进,支持重载非常简单。小心应对并记录所做的工作,对于由此而来的方便的简写形式来说,代价非常公道。

扫描关注微信公众号