目录
第一章:起步
1.1 什么是Groovy
Groovy是轻量级、限制较少、动态的、面向对象的语言,并且运行在JVM上。保留了Java程序员熟悉的语法。Groovy编译为Java字节码,并且扩充了Java API和类库。
1.2 安装
安装groovy之前必须安装Java JDK, 并配置好JAVA_HOME和path
groovy下载链接:https://groovy.apache.org/download.html
解压到文件夹后,配置环境变量 GROOVY_HOME和path
1.3 使用grovvysh
在dos窗口输入 groovysh, 会调出一个shell,可以输入groovy代码并运行
C:\Users\user>groovysh
Groovy Shell (3.0.4, JVM: 14.0.1)
Type ':help' or ':h' for help.
-----------------------------------------------------------------------------------------------------------------------
groovy:000> Math.sqrt(16)
===> 4.0
groovy:000> println 'Test Groovy'
Test Groovy
===> null
groovy:000> String.metaClass.isPalindrome = {
groovy:001> delegate == delegate.reverse()
groovy:002> }
===> groovysh_evaluate$_run_closure1@7e50eeb9
groovy:000> 'mom'.isPalindrome()
===> true
groovy:000> 'mom'.length()
===> 3
groovy:000>
1.4 使用GUI
groovy也自带图形界面(Graphical User Interface, CUI), 在groovy安装路径bin文件夹下双击groovyConsole.bat文件即可打开
使用ctrl+Enter或ctrl+R即可执行代码
1.5 在命令行运行Groovy
可以输入groovy命令后面加上文件名即可,当然事先要创建这个文件,文件里要有groovy代码, 如果是linux命令行,则可先使用cat hello.groovy命令创建文件
C:\Users\user\Desktop\test>groovy hello.groovy
Hello Groovy
如果想直接执行一些语句,请使用-e命令
C:\Users\user>groovy -e "println 'Hello Groovy'"
Hello Groovy
1.6 使用IDE
- IntelliJ IDEA在免费的社区版中对Groovy提供了良好支持,下载链接:https://www.jetbrains.com/idea/download/#section=windows
- Eclipse用户可以使用Groovy Eclipse插件,进行编译运行和测试,还可以在Eclipse内调用Groovy Shell或Groovy控制台
- Mac的程序员普遍是在TestMate中使用Groovy Bundle
第二章:面向Java开发者的Groovy
因为Groovy支持Java语法,并保留了Java语义,我们尽可以随心所欲的混用两种语言。以熟悉的背景为起点,向更符合Groovy的风格过渡,我们会看到Groovy更为简洁,而且更具表现力。在本章最后,会研究一些陷阱——如果预料不到,这些陷阱会令我们措手不及。
2.1 从Java到Groovy
2.1.1 Hello Groovy
从一个简单的Java代码例子开始,它同时也是Groovy代码,保存在一个名为Greeting.groovy的文件中
public class Greeting {
public static void main(String[] args){
for(int i = 0; i < 3; i++){
System.out.print("ho ");
}
System.out.println("Merry Groovy");
}
}
使用groovy Greeting.groovy命令执行这段代码,输出如下
ho ho ho Merry Groovy
执行这一简单的步骤Java代码多,Groovy的信噪比Java高,可以使用更简单的代码获得更多结果。
- 从去掉分号和类方法定义开始
for(int i = 0; i < 3; i++ ){
System.out.print("oh ")
}
System.out.println("Merry Groovy")
- Groovy能够理解println()是因为该方法已经被添加到java.lang.Object中
- Groovy可以使用Range对象、更为轻量级的for循环形式
- 而且Groovy对括号十分宽容
上面代码还可以简化为
for(i in 0..2){print 'oh '}
println 'Merry Groovy'
- 在编写Groovy代码时,不必导入所有的常用类包。例如,使用Calender,就可以毫无困难的引用java.util.Calender
- Groovy自动导入下列包: java.lang、java.util、java.io、java.net
- 它也会导入java.math.BigDecimal和java.math.bigInteger两个类
- 此外,它还导入了groovy.lang, groovy.Util等groovy包
2.1.2 实现循环的方式
range 0…2
我们已经在上一节的代码 for循环中使用过range 0…2
for(i in 0..2){print 'oh '}
println 'Merry Groovy'
Groovy还为我们提供了很多优雅的循环方式
upto()
它是Groovy向java.lang.Integer类中添加的一个便于使用的实例方法,可用于迭代
0.upto(2){print "$it "}
注意是双引号,单引号无法生效
这里的0就是Integer的一个实例,输出是所选范围的每一个值
0 1 2
i t 代 表 进 行 循 环 时 的 索 引 值 。 u p t o ( ) 方 法 接 受 一 个 闭 包 作 为 参 数 。 如 果 闭 包 只 需 要 一 个 参 数 , 在 G r o o v y 中 则 可 以 使 用 默 认 的 i t 来 表 示 该 参 数 [ 第 四 章 将 会 更 详 细 的 讨 论 闭 包 ] 变 量 前 面 的 it代表进行循环时的索引值。 upto()方法接受一个闭包作为参数。如果闭包只需要一个参数,在Groovy中则可以使用默认的it来表示该参数 [第四章将会更详细的讨论闭包] 变量前面的 it代表进行循环时的索引值。upto()方法接受一个闭包作为参数。如果闭包只需要一个参数,在Groovy中则可以使用默认的it来表示该参数[第四章将会更详细的讨论闭包]变量前面的让print()方法打印该变量的值,而非打印这两个字符。利用该特性,我们可以在字符串中嵌入表达式 [第五章有此类用法]
times()
使用upto()方法时,可以设置范围的上限和下限。如果范围从0开始,可以使用times()
5.times{ print "$it "}
0 1 2 3 4
step()
要在循环时跳过一些值,可以使用step()方法
0.step(10,2){print "$it "}
0 2 4 6 8
1.step(10,2){print "$it "}
1 3 5 7 9
下面用现在所学到的方法重写Greeting这个例子。与我们一开始的Java代码相比,体会Groovy代码是多么的简短
3.times { print "oh "}
println 'Merry Groovy!'
oh oh oh Merry Groovy
2.1.3 GDK一瞥
Java平台的核心优势之一就是Java开发包(JDK). Groovy向JDK的各种类中添加便捷方法,扩展了JDK。这些扩展可以在GDK中获得。
例:版本控制系统维护,每当有文件签入,后端钩子会使用一些规则,执行进程,然后发出通知。简而言之,必须创建进程,并与这些进程交互。
Java中可以使用java.lang.Process与系统进程交互。假设我们想在代码中调用Subversion的help, 下面是实现该功能的java代码:
package com.test;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
public class executeProcess{
public static void main(String[] args){
try {
Process proc = Runtime.getRuntime().exec("svn help");
BufferedReader result = new BufferedReader(
new InputStreamReader(proc.getInputStream()));
String line;
while ((line = result.readLine()) != null) {
System.out.println(line);
}
}catch (IOException e) {
e.printStackTrace();
}
}
}
java.lang.Process类非常有用,但是在前面的代码中使用它时,真的大费周章;
而通过在java.lang.String类上添加一个execute()方法,GDK使这一切变得非常简单了。
println "svn help".execute().text
除了使用Subversion,还可以尝试其他命令:只需要将svn help替换成其他程序,比如groovy -v
println "groovy -v".execute().text
2.1.4 安全导航操作符(.?)
Groovy有很多激动人心且能帮助简化开发工作的小特性。安全导航(safe-navigation)操作符(?.)就是其中之一。我们经常需要检查引用是否为控制(null)。这种操作单调乏味。
如下例子,使用该操作符,可以避免这种操作:
def foo(str){
//if(str != null){ str.reverse() }
str?.reverse()
}
println foo("evil")
println foo(null)
live
null
?.操作符只有在引用不为null时才会调用指定的方法或属性
使用?.在空引用上调用reverse(),其结果是产生了一个null,而没有抛出空指针异常,这是Groovy减少噪音、节省开发者力气的一种手段。
2.1.5异常处理
与Java相比,Groovy少了很多繁文缛节。这一点上异常处理体现的及其明显。
Java强制我们处理所有受检查异常(Checked Exception)
比如:我们想调用Thread的sleep()方法,(Groovy提供了一个备选的sleep方法,参见7.1.3)。
Java坚持让我们捕获java.lang.InterruptedException,程序员不得已大量空的catch
//Java代码
try {
Thread.sleep(5000);
}catch(InterruptedException e) {
//不知道做什么
e.printStackTrace();
}
对于那些我们不想处理,或者不适合在代码当前层进行处理的异常,Groovy并不强制我们处理。我们不处理的任何异常都会被自动传递给更高一层
def openFile(fileName){
new FileInputStream(fileName)
}
openFile()方法没有处理FileNotFountException异常。如果产生了该异常,它并不会被压制下来,而是会被传递给调用代码,由调用代码来处理
try{
openFile("nonexistentFile")
}catch(FileNotFountException e){
println "Oops:" + e
}
可以使用Exception来接收处理任何异常,但是注意它不能捕获Excepton之外的Error或者Throwable。要捕获所有这些,请使用catch(Throwable throwable)
2.1.6 Groovy是轻量级的Java
Groovy还有其他一些使这门语言更为轻量级、更为易用的特性,如下:
- return语句几乎总是可选的(参见2.11.1)
- 尽管可以使用分号分隔语句,但它几乎总是可选的(参见2.11.6)
- 方法和类默认是公开(public)的
- ?.操作符只有对象引用不为空时才uhi分派调用
- 可以使用具名参数初始化JavaBean(参见2.2)
- Groovy不强迫我们捕获自己不关心的异常,这些异常会被传递给代码的调用者
- 静态方法可以使用this来引用Class对象。在下面的例子中,learn()方法返回的是Class对象,所以可以使用链式调用:
class Wizard {
def static learn(trick, action) {
//...
this
}
}
Wizard.learn('alohomora',{/*...*/})
.learn('expelliatmus',{/*....*/})
.learn('lumos',{/*...*/})
2.2 JavaBean
按照特定约定暴露出其属性的java对象就被视作JavaBean,但是要访问这些属性,开发者要调用访问器(Getter)和更改器(Setter)。要创建很多傻瓜方法。Groovy中JavaBean获得了应有的尊重。
public class Car {
private int miles;
private final int year;
//构造函数
public Car(int theYear) { year = theYear; }
//Getter
public int getMiles() {return miles;}
//Setter
public void setMiles(int theMiles) {miles = theMiles;}
public int getYear() {return year;}
public static void main(String[] args) {
Car car = new Car(2008);
System.out.println("Year:"+car.getYear());
System.out.println("Miles:"+car.getMiles());
System.out.println("Setting Miles");
car.setMiles(20000);
System.out.println("Miles:"+car.getMiles());
}
}
Year:2008
Miles:0
Setting Miles
Miles:20000
前面这段代码可以在groovy中运行,但是如果用groovy重写,可以省去很多方法
class Car{
def miles = 0
final year
Car(theYear){ year = theYear }
}
Car car = new Car(2008)
println "Year: $car.year"
println "Miles: $car.miles"
println "Setting Miles"
car.miles = 20000
println "Miles:$car.miles"
Grooby会在背后默默的创建一个访问器(Getter)和一个更改器(Setter)
如果想把属性设置为只读的,需要使用final来声明该属性,这和java中一样,这种情况下,Groovy会为该属性提供一个访问器,但不提供更改器。
此时修改final修饰的字段的任何操作都将导致异常
Groovy的实现不区分public、private、protected
想把属性设为私有的,Groovy不遵守private之类的修饰,因此,必须再实现一个拒绝任何修改的修改器setter
class Car{
final year
private miles = 0
Car(theYear){ year = theYear }
private void setMiles(miles){
throw new IllegalAccessException("you're not allwoed to change this field")
}
def drive(dist){ if(dist>0) miles += dist}
}
Car car = new Car(2020);
println "Year:$car.year"
println "Miles:$car.miles"
println "drive 1000 miles now"
car.drive(1000)
println "Miles:$car.miles"
try{
println "set year directly"
car.year = 2000
println "Year:$car.year"
}catch(Exception e){
println e.message
}
try{
println "set miles directly"
car.miles = 20000
println "Miles:$car.miles"
}catch(Exception e){
println e.message
}
Year:2020
Miles:0
drive 1000 miles now
Miles:1000
set year directly
Cannot set readonly property: year for class: Car
set miles directly
you're not allwoed to change this field
从输出可以看出,我们可以读取两个属性的值,但是不能设置其中任何一个
请谨慎使用class属性,像Map、Builder等一些类对该属性有特殊处理(参见6.5章)
为避免任何意外,一般使用getClass(),而不是class
Calendar.instance
//代替Calendar.getInstance()
str = "hello"
str.class.name
//代替str.getClass().getName()
//注意:不能用于Map,Builder等类型
//保险起见,请使用str.getClass().name
2.3 灵活初始化与具名参数(具名参数在构造器和方法中的使用)
Groovy中可以灵活地初始化一个JavaBean类
构造对象时,可以简单的以逗号分隔的名值对来给出属性
class Robot{
def type, height, width
def access(location,weight, fragile){
println "Received fragile? $fragile, weight: $weight, loc: $location"
}
}
Robot robot = new Robot(type: "arm", width: 10, height: 40)//具名参数构建对象
println "$robot.type, $robot.height, $robot.width"
//1.实参形式
robot.access(30,50,true)//实参必须要按顺序
//2.具名参数(名值对)
//除具名参数之外的参数也要按顺序,名值对在Map内也是具有顺序的
robot.access(x:30,y:20,z:10,50, true)
//可以修改参数顺序
robot.access(50,true,x:30,y:20,z:10)
//3.这种情况就会报错
robot.access(30,20,10,50,true)
arm, 40, 10
Received fragile? true, weight: 50, loc: 30
Received fragile? true, weight: 50, loc: [x:30, y:20, z:10]
Received fragile? true, weight: 50, loc: [x:30, y:20, z:10]
Exception thrown
要使此类操作正确执行,类中必须有一个无参构造器。如果没有定义构造函数,则类自带默认无参构造函数,如果定义了一个带参构造函数,则其默认无参构造函数会被覆盖,需要重写无参构造函数
如果给的实参个数多于方法的形参个数,而且多出的实参是名值对,那么Groovy会假设方法的第一个形参是Map, 然后将实参列表中的所有名值对组织到一起,作为第一个参数的值。之后再将剩下的实参按照给出的顺序赋给其余形参
这种灵活性非常强大,但是可能会给人带来困惑,请谨慎使用
这个例子中,如果传递的是3个整型实参,这种情况下编译器将按顺序传递实参,而不会在实参中创建映射。
2.4 可选形参(方法和构造器中)
Groovy中可以把方法和构造器的形参设为可选的。实际上,我们想设置多少就可以设置多少,但这些形参必须位于形参列表的末尾。
要定义可选形参,只需要在形参列表中给它赋上一个默认值值。如果调用不提供相应实参,则假定为默认值。
可选形参:
- 方法或构造器的形参列表末尾
- 在形参列表中给它赋上一个默认值
利用这一特性,可以在演进式设计中向已有方法添加新的形参
def log(x, base = 10){
Math.log(x) / Math.log(base)
}
println log(1024)
println log(1024,10)
println log(1024,2)
3.0102999566398116
3.0102999566398116
10.0
末位数组形参作为可选形参
Groovy还会把末尾的数组形参视作可选的。可以为最后一个形参提供零个或多个值
def task(name,String[] details){
println "$name - $details"
}
task "Call","123-456"
task "Call","123-456","456-789"
task "Check Mail"
Call - [123-456]
Call - [123-456, 456-789]
Check Mail - []
Groovy会把末尾的实参收集起来,赋给数组形参
2.5 使用多赋值
方法返回多结果
向方法传递多个参数很常见,但是从方法返回多个结果,尽管可能很实用但是不常见。
其实方法返回的是数组,将多个变量以逗号分隔,放在圆括号中,至于赋值表达式左侧即可按数组元素顺序赋值
def splitName(fullName) {
fullName.split(' ')
}
def (firstName, lastName) = splitName('James Bond')
println "$lastName, $firstName $lastName"
Bond, James Bond
将结果直接设置到两个变量中,不必创建临时变量并编写多条赋值语句
无需中间变量的交换变量
利用该特性来交换变量,无需创建中间变量来保存被减缓的值,只需将欲交换的变量放在圆括号内,置于赋值表达式左侧,同时将它们以象返的顺序放于方括号内,置于右侧即可。
def name1 = "Tom"
def name2 = "Jerry"
println "$name1 and $name2"
(name1, name2) = [name2, name1]
println "$name1 and $name2"
Tom and Jerry
Jerry and Tom
以上两种情况(方法返回多结果 和 变量交换) ,赋值表达式左边变量与右边的值数目都是相等的
当变量与值数目不匹配时,Groovy也可以优雅的处理。
- 如果有多余的变量,Groovy会将它们设为null;
- 如果有多余的值,则会被丢弃
【例】
def (String cat, String mouse) = ['Tom','Jerry','Spike','Jeff']
println "$cat and $mouse"//Spike和Jeff将会被丢弃
def (first, second, third) = ['Tom','Jerry']
println "$first, $second, and $third"
Tom and Jerry
Tom, Jerry, and null
如果多余的变量是不能设置为null的基本类型,Grooovy将会抛出一个异常
2.6 实现接口
在Groovy中,可以把一个映射或者一个代码块转化为接口,因此可以快速实现带有多个方法的接口。
as操作符实现接口
下面是用于向Swing的JButton注册时间处理器的Java代码。调用addActionListener()方法时, 需要实现一个ActionListener接口的实例。其中创建了一个实现了该接口的匿名内部类,同时提供了所需的actionPerformed()方法。该方法要求提供一个ActionEvent实例作为参数
button.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent ae) {
JOptionPane.showMessageDialog(frame, "You Clicked!");
}
});
Groovy提供了一个与之不同的惯用法,不需要actionPerformed()方法声明,也不需要显示的用new来创建匿名内部类的实例
button.addActionListener(
{ JOptionPane.showMessageDialog(frame,"You Clicked!") } as ActionListener
)
调用了addActionListener方法,同时为该方法提供了一个代码块,借助as操作符,相当于实现了ActionListener接口
Groovy会自动处理剩下的工作。它会拦截对接口中任何方法的调用(这个例子就是actionPerformed()),然后将调用路由到我们提供的代码块。要运行这段代码快,还需要创建窗体(Frame)及其组件。
含有多个方法的接口的实现
而对于有多个方法的接口,如果打算为其所有方法提供一个相同的实现,和上面一样,不需要特殊的操作
【例】
假设想实现一个功能,随着鼠标被点击或者在应用中移动而显示鼠标的位置。
在Java中,我们必须实现MouseListener和MouseMotionListener接口中的总共七个方法。
因为Groovy对所有这些方法的实现都是相同的,所以十分方便
displayMouseLocation = { PositionLabel.setTest("$it.x, $it.y") }
frame.addMouseListener(displayMouseLocation as MouseListener)
frame.addMouseMotionListener(displayMouseLocation as MouseMotionListener)
这段代码创建了变量displayMouseLocation,它指向的是一个代码块。
使用as操作符将其转化了2次,分别转化为MouseListener和MouseMotionListener
上面的例子中又出现了it变量。it表示方法的参数。如果正在实现的一个接口中的方法需要多个参数,那么可以将其分别定义为独立的参数,也可以定义为一个数组类型的参数,具体情况将在第四章讨论
**Groovy没有强制实现接口中的所有方法:可以只定义自己关心的,而不考虑其他方法。**如果剩下的方法从来都不会被调用,那也就没有必要去实现这些方法了。
使用(键:值)的风格 为接口中的方法做不同的实现
但是在大多数实际情况下, 接口中的每个方法需要不同的实现。
只需要创建一个映射,以每个方法的名字作为键,以方法对应的代码体作为值,同时使用冒号(:)分割方法名和代码块即可。
此外,不必实现所有方法,只需要实现真正关系的那些即可。如果未予实现的方法从未被调用过,没有必要浪费精力去实现这些伪存根。如果没提供实现的方法被调用了,则会出现NullPointerException
handleFocus = {
focusGained : { msgLabel.setText("Good To See You!") },
focusLost : { msgLabel.setTest("Come back soon!") }
}
button,.addFocusListener(handleFocus as FocusListener)
每当例子中的按钮获得焦点时,与focusGained键关联的第一个代码就会被调用。
当按钮失去焦点时,与focusLost键关联的代码块则会被调用。
这里的键相当于FocusListener接口中的方法
使用asType()方式动态实现接口
如果知道所实现接口的名字,使用as操作符即可,但如果应用要求的行为是动态的,而且只有在运行时才能知道接口的名字。则需要使用asType()方法,通过将想要实现接口的Class元对象作为一个参数发送给asType(),可以把代码块或映射转化为接口。
events = [ 'WindowListener', 'ComponentListener']
//上面的列表可能是动态的,而且可能来自某些输入
handler = { msgLabel.setText("&it") }
for (event in events) {
handlerImpl = handler.asType(Class.forName("java.awt.event.${event}"))
frame."add${event}"(handlerImpl)
}
想实现的接口在在列表events中,该列表是动态的,假设它会在代码执行期间通过输入来填充。事件公共的处理器位于变量handler指向的代码块中。我们对事件进行循环,对于每个事件都使用了asType()方法为该接口创建一个实现。在代码块上调用asType方法,同时把使用forName()方法获得的、该接口的Class对象传给它。一旦有了监听器接口的实现,就可以通过调用相应的add方法(如addWindowListener())来注册该实现。调用add方法本身就是动态的。(11.2节将介绍这些方法的更多细节)
本节完整代码
import javax.swing.*
import java.awt.*
import java.awt.event.*
frame = new JFrame(
size: [300,300],
layout: new FlowLayout(),
defaultCloseOperation: javax.swing.WindowConstants.EXIT_ON_CLOSE
)
button = new JButton("click")
positionLabel = new JLabel("")
msgLabel = new JLabel("")
frame.contentPane.add button
frame.contentPane.add positionLabel
frame.contentPane.add msgLabel
button.addActionListener(
{ JOptionPane.showMessageDialog(frame, "You clicked!") } as ActionListener
)
displayMouseLocation = { positionLabel.setText("$it.x, $it.y") }
frame.addMouseListener(displayMouseLocation as MouseListener)
frame.addMouseMotionListener(displayMouseLocation as MouseMotionListener)
handleFocus = [
focusGained : { msgLabel.setText("Good to see you!") },
focusLost : { msgLabel.setText("Come Back Soon!") }
]
button.addFocusListener(handleFocus as FocusListener)
events = [ 'WindowListener', 'ComponentListener']
//上面的列表可能是动态的,而且可能来自某些输入
handler = { msgLabel.setText("$it") }
for (event in events) {
handlerImpl = handler.asType(Class.forName("java.awt.event.${event}"))
frame."add${event}"(handlerImpl)
}
frame.show()
2.7 布尔值
Groovy中的布尔值与Java不同。根据上下文,Groovy会自动把表达式计算为布尔值。
Java要求if语句的条件部分必须是一个布尔表达式, 比如
- if(obj != null)
- if (val > 0)
Groovy则会尝试推断, 会检查该引用是否为null, 它将nll视作false, 将非null得值视作true
str = "hello"
if (str) { println 'hello' }
hello
如果对象引用不为null,表达式得结果还与对象得类型有关。例如,如果对象是一个集合(如java.util.ArrayList),那么Groovy会检查该集合是否为空。因此在这种情况下,只有当obj不为null且该集合至少包含一个元素时,if(obj)才会被计算为true
lst0 = null
println lst0 ?'list0 true' : 'list0 false'
lst1 = [1,2,3]
println lst1 ?'list1 true' : 'list1 false'
lst2 = []
println lst2 ?'list2 true' : 'list2 false'
list0 false
list1 true
list2 false
内建步尔求值约定(表格)
类型 | 为真的条件 |
---|---|
Boolean | 值为true |
Collection | 集合不为空 |
Character | 值不为0 |
CharSequence | 长度大于0 |
Enumeration | Has More Elements()为true |
Iterator | hasNext()为true |
Number | Double值不为0 |
Map | 该映射不为空 |
Matcher | 至少有一个匹配 |
Object[] | 长度大于0 |
其他任何类型 | 引用不为null |
asBoolean()自定布尔转换
除了使用Groovy内建的布尔求值约定,在自己的类中,还可以通过实现asBoolean()方法来编写自己的布尔转换
2.8 操作符重载
每个操作符都会映射一个标准的方法。在Java中可以使用那些方法;而在Groovy中,既可以使用操作符,也可以使用与之对应的方法。
String类中操作符的重载
下面是一个演示操作符重载的例子:
for (ch = 'a'; ch < 'd'; ch++){
println ch
}
abc
我们通过++操作实现了从字符a到c的循环。该操作符映射的是String类的next()方法
Groovy中还可以使用简洁的for-each语法,不过两种实现都用到了String类的next()方法
for (ch in 'a'..'c' ){
println ch
}
集合类中重载的操作符
String类重载了很多操作符,5.4节将于一介绍。类似的为方便使用,集合类(如ArrayList和Map)也重载了一些操作符。
要向集合中添加元素,可以使用<<操作符,该操作符会被转换为Groovy在Collection上添加的leftShift()方法
lst = ['hello']
lst << 'there'
println lst
[hello, there]
为自己的类提供操作符(映射方法)
通过添加映射方法,我们可以为自己的类提供操作符,比如为+操作符添加plus()方法
class ComplexNumber {
def real, imaginary
def plus(other) {
new ComplexNumber(real : real + other.real, imaginary : imaginary + other.imaginary)
}
String toString() {"$real ${imaginary > 0 ? '+' : ''} ${imaginary}i"}
}
c1 = new ComplexNumber(real : 1, imaginary: 2)
c2 = new ComplexNumber(real : 4, imaginary: 1)
println c1
println c2
println c1 + c2
1 + 2i
4 + 1i
5 + 3i
ComplexNumber类重载了+操作符。
复数有实部和虚部,就像人们的收入有实际输入和个人所得税申报单上的收入之分一样
因为在ComplexNumber类上添加了plus()方法,所以可以使用+操作符把两个复数加到一起,得到又一个作为结果的复数
在重载时,必须保留预期的语义。例如,+操作符不可以修改操作中的任何一个操作数。如果操作符必须是可交换的、对称对的或传递的,则必须确保重载的方法遵循这些特性。