迄今为止,您已经用 Groovy 输出了许多次 “Hello World”,已经操作了集合,用闭包在集合上迭代,也定义了您自己的闭包。做所有这些工作时,甚至还没有讨论那个对 Java 开发人员来说至关重要的概念 — 类。
当然,您已经在这个教程中使用过类了:您编写的最后几个示例就是在不同类的 main()
方法中。而且,您已经知道,在 Groovy 中可以像在 Java 代码中一样定义类。惟一的区别是,不需要使用 public
修改符,而且还可以省略方法参数的类型。这一节将介绍使用 Groovy 类能够进行的其他所有操作。
Song 类
我们先从用 Groovy 定义一个简单的 JavaBean 形式的类开始,这个类称为 Song
。
第一步自然是用 Groovy 创建名为 Song
的类。这次还要为它创建一个包结构 — 创建一个包名,例如 org.acme.groovy
。
创建这个类之后,删除 Groovy 插件自动生成的 main()
。
歌曲有一些属性 — 创作歌曲的艺术家、歌曲名称、风格等等。请将这些属性加入新建的 Song
类,如下所示:
package org.acme.groovy |
迄今为止还不错,是不是?对于 Grooovy 的新开发人员来说,还不算太复杂!
Groovy 类就是 Java 类
应该还记得本教程前面说过 Groovy 编译器为用 Groovy 定义的每个类都生成标准的 Java .class
。还记得如何用 Groovy 创建 HelloWorld
类、找到 .class
文件并运行它么?也可以用新定义的 Song
类完成同样的操作。如果通过 Groovy 的 groovyc
编译器编译代码(Eclipse Groovy 插件已经这样做了),就会生成一个 Song
.class
文件。
这意味着,如果想在另一个 Groovy 类或 Java 类中使用新建的 Song
类,则必须导入 它(当然,除非使用 Song
的代码与 Song
在同一个包内)。
接下来创建一个新类,名为 SongExample
,将其放在另一个包结构内,假设是 org.thirdparty.lib
。
现在应该看到如下所示的代码:
package org.thirdparty.lib |
类的关系
现在是使用 Song
类的时候了。首先导入实例,并将下面的代码添加到 SongExample
的 main()
方法中。
package org.thirdparty.lib |
现在 Song
实例创建完成了!但是仔细看看以前定义的 Song
类的初始化代码,是否注意到什么特殊之处?您应该注意到自动生成了构造函数。
类初始化
Groovy 自动提供一个构造函数,构造函数接受一个名称-值对的映射,这些名称-值对与类的属性相对应。这是 Groovy 的一项开箱即用的功能 — 用于类中定义的任何属性,Groovy 允许将存储了大量值的映射传给构造函数。映射的这种用法很有意义,例如,您不用初始化对象的每个属性。
也可以添加下面这样的代码:
def sng2 = new Song(name:"Kung Fu Fighting", genre:"Disco") |
也可以像下面这样直接操纵类的属性:
def sng3 = new Song() |
从这个代码中明显可以看出,Groovy 不仅创建了一个构造函数,允许传入属性及其值的映射,还可以通过 .
语法间接地访问属性。而且,Groovy 还生成了标准的 setter 和 getter 方法。
在进行属性操纵时,非常有 Groovy 特色的是:总是会调用 setter 和 getter 方法 — 即使直接通过 .
语法访问属性也是如此。
核心的灵活性
Groovy 是一种本质上就很灵活的语言。例如,看看从前面的代码中将 setGenre()
方法调用的括号删除之后会怎么样,如下所示:
sng3.setGenre "Disco" |
在 Groovy 中,对于接受参数的方法,可以省略括号 — 在某些方面,这样做会让代码更容易阅读。
方法覆盖
迄今为止已经成功地创建了 Song
类的一些实例。但是,它们还没有做什么有趣的事情。可以用以下命令输出一个实例:
println sng3 |
在 Java 中这样只会输出所有对象的默认 toString()
实现,也就是类名和它的 hashcode(即 org.acme.groovy.Song@44f787
)。下面来看看如何覆盖默认的 toString()
实现,让输出效果更好。
在 Song
类中,添加以下代码:
String toString(){ |
根据本教程已经学到的内容,可以省略 toString()
方法上的 public
修改符。仍然需要指定返回类型(String
),以便实际地覆盖正确的方法。方法体的定义很简洁 — 但 return
语句在哪?
不需要 return
您可能已经想到:在 Groovy 中可以省略 return
语句。Groovy 默认返回方法的最后一行。所以在这个示例中,返回包含类属性的 String
。
重新运行 SongExample
类,应该会看到更有趣的内容。toString() 方法返回一个描述,而不是 hashcode。
特殊访问
Groovy 的自动生成功能对于一些功能来说很方便,但有些时候需要覆盖默认的行为。例如,假设需要覆盖 Song
类中 getGenre()
方法,让返回的 String
全部为大写形式。
提供这个新行为很容易,只要定义 getGenre()
方法即可。可以让方法的声明返回 String
,也可以完全省略它(如果愿意)。下面的操作可能是最简单的:
def getGenre(){ |
同以前一样,这个简单方法省略了返回类型和 return
语句。现在再次运行 SongExample
类。应该会看到一些意外的事情 —— 出现了空指针异常。
空指针安全性
如果您一直在跟随本教程,那么应该已经在 SongExample
类中加入了下面的代码:
assert sng3.genre == "Disco" |
结果在重新运行 SongExample
时出现了断言错误 — 这正是为什么在 Eclipse 控制台上输出了丑陋的红色文字。(很抱歉使用了这么一个糟糕的技巧)
幸运的是,可以轻松地修复这个错误:只要在 SongExample
类中添加以下代码:
println sng2.artist.toUpperCase() |
但是现在控制台上出现了更多的 红色文本 — 出什么事了?!
可恶的 null
如果回忆一下,就会想起 sng2
实例没有定义 artist
值。所以,在调用 toUpperCase()
方法时就会生成 Nullpointer
异常。
幸运的是, Groovy 通过 ?
操作符提供了一个安全网 — 在方法调用前面添加一个 ?
就相当于在调用前面放了一个条件,可以防止在 null 对象上调用方法。
例如,将 sng2.artist.toUpperCase()
行替换成 sng2.artist?.toUpperCase()
。请注意,也可以省略后面的括号。(Groovy 实际上也允许在不带参数的方法上省略括号。不过,如果 Groovy 认为您要访问类的属性而不是方法,那么这样做可能会造成问题。)
重新运行 SongExample
类,您会发现 ?
操作符很有用。在这个示例中,没有出现可恶的异常。现在将下面的代码放在这个类内,再次运行代码。
def sng4 = new Song(name:"Thriller", artist:"Michael Jackson") |
就是 Java
您将会注意到,虽然预期可能有异常,但是没有生成异常。即使没有定义 genre
,getGenre()
方法也会调用 toUpperCase()
。
您还记得 Groovy 就是 Java,对吧?所以在 Song
的 toString()
中,引用了 genre
属性本身,所以不会调用 getGenre()
。现在更改 toString()
方法以使用 getGenre()
,然后再看看程序运行的结果。
String toString(){ |
重新运行 SongExample
,出现类似的异常。现在,请自己尝试修复这个问题,看看会发生什么。
另一个方便的小操作符
希望您做的修改与我的类似。在下面将会看到,我进一步扩充了 Song
类的 getGenre()
方法,以利用 Groovy 中方便的 ?
操作符。
def getGenre(){ |
?
操作符时刻都非常有用,可以极大地减少条件语句。