Groovy学习笔记


第一章:起步


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来表示该参数 [第四章将会更详细的讨论闭包] 变量前面的 itupto()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
EnumerationHas More Elements()为true
IteratorhasNext()为true
NumberDouble值不为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()方法,所以可以使用+操作符把两个复数加到一起,得到又一个作为结果的复数

在重载时,必须保留预期的语义。例如,+操作符不可以修改操作中的任何一个操作数。如果操作符必须是可交换的、对称对的或传递的,则必须确保重载的方法遵循这些特性。


  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值