美妙的操作符

 
Java™ 取消了操作符重载,但是新兴的 Groovy 又使之浮出水面。在 实战 Groovy 定期连载的“Groovy 每日应用”的最后一期中,请随着 Andrew Glover 介绍的三类可重载操作符,重新寻回自己多年来失去的东西。

许多以前使用 C++ 的开发人员会怀念操作符重载,例如 +-。虽然它们很方便,但是被覆盖的操作符的多态实质会造成混淆,所以操作符重载在 Java 语言中被取消了。这个限制的好处是清晰:Java 开发人员不必猜想两个对象上的 + 是把它们加在一起还是把一个对象附加到另一个对象上。不好的地方则是丧失了一个有价值的简写形式。

关于本系列

把任何一个工具用于开发实践的关键是,知道什么时候使用它而什么时候把它留在工具箱中。脚本语言(或动态语言)可以是工具箱中极为强大的工具,但是只有在恰当地应用到适当的场景中才是这样。为了这个目标,实战 Groovy 是一系列文章,专门介绍 Groovy 的实际应用,并教给您什么时候、如何成功地应用它们。

现在,期望放任自由的 Groovy 把这个简写形式带回来!在 实战 Groovy 的这一期中,我将介绍 Groovy 对操作符即时多态(也称为操作符重载)的支持。正如 C++ 开发人员会告诉您的,这个东西既方便又有趣,虽然必须小心谨慎才能接近。

三类可重载操作符

我把 Groovy 的可重载操作符分成三个逻辑组: 比较操作符、算术类操作符和数组类操作符。这几组只涵盖了普通 Java 编程中可用操作符的一个子集。例如,像 &^ 这样的逻辑操作符目前在 Groovy 中不可用。

表 1 显示了 Groovy 中可用的三组可重载操作符:


表 1. Groovy 的可重载操作符
1. 比较操作符对应着普通的 Java equalscompareTo 实现
2. Java 的算术类操作符,例如 +-*
3. 数组存取类操作符 []






比较操作符

比较操作符对应着 Java 语言中的 equalscompareTo 实现,通常用作集合中排序的快捷方式。表 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",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

注意 lamp4lamp5 是如何相同的,以及其他对象之间具有什么样的细微差异。因为 lamp1baseColorBlacklamp2baseColorBlue,所以 <=> 返回 1(black 中的 a 比 blue 中的 u 靠前)。类似地,lamp3lavaColorBlue,而 lamp1 的是 Red。因为条件 lamp1 <=> lamp3 返回 -1,所以语句 lamp1 < lamp3 返回 truellamp4llamp5 是相等的,所以 <=> 返回 0

而且,可以看到 == 也适用于对象相等。lamp4lamp5 是同一对象。当然,我应当用 assert lamp4.equals(lamp5) 证实这一点,但是 == 更快捷!

我想要我的引用相等

现在,如果真的想测试对象引用相等该怎么办?显然,我不能用 ==,但是 Groovy 为这类事情提供了 is() 方法,如清单 3 所示:


清单 3. is() 的作用!
lamp6 = new LavaLamp(model:"1344",
liquidColor:"Clear",lavaColor:"Purple")
lamp7 = lamp6
assert lamp7.is(lamp6)

在清单 3 中可以看出,lamp6lamp7 是相同的引用,所以 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
}

现在我实现了 getAtputAt,我可以使用 [] 语法了,如清单 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 类进行改进,支持重载非常简单。小心应对并记录所做的工作,对于由此而来的方便的简写形式来说,代价非常公道。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


背景:    8月29日,凌晨4点左右,某服务告警,其中一个节点直接down掉,收到告警的同事让运维重启。    9点左右,内存监控上发现内存异常,堆内存涨速很快,即便GC也没有什么效果,频繁GC。    9点38,服务各种超时,影响整个app使用。处理方式:    当时由于很想要
https://support.smartbear.comeadyapi/docs/soapui/steps/groovy.htmlGettestcaseobjectToobtaintheobjectwhichreferstothecontainingtestcase,usethefollowingcodesnippet:Groovy def case=testRunner.testCase; Byusingthe tes
有几个选项可用于执行自定义JMeter脚本并扩展基线JMeter功能。查看最流行的扩展机制,比较性能并分析哪一个是最好的。  这是乐队之战,JMeter风格。 BeanshellV.JSR223V.JavaRequestSampler 在我们之前的帖子中,  JMeterPerformance和TuningTips  ( 由fantastik
Scala和Java为静态语言,Groovy为动态语言Scala:函数式编程,同时支持面向对象Groovy:jvm上的脚本,较好兼容java语法,Groovy加强了Java集成。 可配置化的优势,可以将一些简单的逻辑公开给外部编辑和使用,增强了互操作性,复杂逻辑来说,可配置化代码的调试则会比较麻烦 Scala和Java
出处:https://www.jianshu.com/p/ce6f8a1f66f4一、一些内部元件的访问testRunner.testCase开头1、向下访问testRunner.testCase.testSteps[testStepName]testRunner.testCase.getTestStepByName("新增一个空间")2、向上访问,用于访问同一项目中的其他testSuites和testCase下
在运行groovy的junit方法时,报了这个错误:java.lang.ExceptionInInitializerError atorg.codehaus.groovy.reflection.ClassInfo.isValidWeakMetaClass(ClassInfo.java:271) atorg.codehaus.groovy.reflection.ClassInfo.getMetaClassForClass(ClassInfo.java:241) atorg.codeha
基本语法1.Grovvy的注释分为//和/**/和java的一样.2.Grovvy语法可以不已分号结尾.3.单引号,里面的内容严格的对应java中的String,不对$符号进行转义.defs1='iamastudent$'printlns1iamastudent$4.双引号“”的内容中如果有$号的话,会先对表达式先求值.de
Tiobe发布了最新一期(3月份)编程语言欢迎度榜单,其榜单根据互联网上有经验的程序员、课程和第三方厂商的数量,并使用搜索引擎(如Google、Bing、Yahoo!)以及Wikipedia、Amazon、YouTube统计出排名数据。TOP5几乎没有变化,Java和C语言牢牢占据前两名。Python相较去年上升一位进入TOP3,C++下
我有一个Google地图组件,作者可以在其中指定纬度和经度.我正在使用带有正则表达式的常规“输入”类型控件来验证它们是否是数字,但是,当试图解决指定范围的问题时(经度验证该值在[-180,180]内并且纬度[-90,90])但是,通过正则表达式进行验证似乎很麻烦,而且利用inputtype=“numb
我正在为未来的应用程序评估SpringBoot,并希望使用Groovy模板来实现其纯粹的可读性.不幸的是,我在迭代我添加到控制器返回的ModelAndView对象的对象列表时遇到了麻烦.这是我的控制器:@RestController@RequestMapping("/ships")publicclassShipsController{@Autowired
我有一个基于Spring的java应用程序,其中包含一些有用的组件.作为系统的一部分,我有一个groovy脚本,来处理一些报告.我想从groovy脚本中调用spring组件.当我用Java编写时,我需要在@Component中使用@Autowired注释,即@ComponentclassReporter{@AutowiredSearchServicesearchS
在Grailsi18n插件definedthusly中定义了一个messageSourcebean:messageSource(PluginAwareResourceBundleMessageSource){basenames=baseNames.toArray()fallbackToSystemLocale=falsepluginManager=manager....}是否可以覆盖我的resources.groovy中的fa
我正在寻找一种方法来反向工程RDBMS表(MSSQLServer)并生成JPA@EntityGroovy类.我们目前没有选择使用Grails和/或GORM,因此Grailsdb-reverse-engineer插件似乎很接近但不太正确.它生成符合GORM的类而不是JPA实体类.我们目前有一个gradle构建,它利用org.hibernate.tool.ant.Hibe
https://blog.csdn.net/Gdeer/article/details/83062523一、直接运行groovy程序因为groovy插件和android插件不兼容,所以不能在原始项目上使用groovy。 新建module,创一个JavaLibrary,取名lib。  修改lib/build.gradleapplyplugin:'java-library'depe
一、自动生成GET请求脚本1、配置Createascript在ngrinder管理台主页,点击script–>Createascript,并填写脚本名称和请求的url,如下所示:点击Create按钮,nGrinder会自动生成对应的脚本结构,如果没有参数需要设置的话,可以直接运行了。二、详细解析GET请求脚本ngrinder自动生成的脚本
我正在关注使用列表和地图作为构造函数的this博文.为什么以下列表无法强制反对?classTest{staticclassTestObject{privateinta=1;protectedintb=2;publicintc=3;intd=4;Strings="s";}stati
Information:java:Errorsoccurredwhilecompilingmodule'security'Information:javac1.8.0_131wasusedtocompilejavasourcesInformation:2019/6/98:31-Buildcompletedwith1errorand0warningsin3s116msError:java:读取E:\repository\org
ngrinder中的groovy脚本结构类似junit,同时在junit的基础之上封装了自己的注解,用来控制脚本的运行。一、运行逻辑图如下:此处只列出了groovy脚本的逻辑,jython脚本是类似的,在此不再单独介绍。二、各注解的使用比较三、关注点在ngrinder中,通常使用单进程多线程就足够大部分测试了,所以:
我有一个switch语句来处理javaenumfoo,并使用spock编写一些groovy单元测试.我已经添加了一个测试,它验证当前是否处理了每种类型的foo而没有抛出异常.现在我想测试一个无法识别的foo类型会导致抛出异常.要做到这一点,我将不得不嘲笑枚举,并已经看到这里概述的解决方案:MockingJ
我有一个groovy实体ClientInvoiceAttachmentExt,它扩展了java实体ClientInvoiceAttachment.ClientInvoiceAttachment具有@Id注释,但仍然看到“没有为实体指定的标识符”错误这是我的堆栈跟踪[Mar0317:11:54]ERROR|org.springframework.web.context.ContextLoader|Contex