当前位置: 代码迷 >> 综合 >> Groovy 中的类
  详细解决方案

Groovy 中的类

热度:66   发布时间:2024-01-10 17:46:29.0

迄今为止,您已经用 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

class Song {
def name
def artist
def genre
}

迄今为止还不错,是不是?对于 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

class SongExample {
static void main(args) {}
}







类的关系

现在是使用 Song 类的时候了。首先导入实例,并将下面的代码添加到 SongExamplemain() 方法中。

package org.thirdparty.lib

import org.acme.groovy.Song

class SongExample {
static void main(args) {
def sng = new Song(name:"Le Freak",
artist:"Chic", genre:"Disco")
}
}

现在 Song 实例创建完成了!但是仔细看看以前定义的 Song 类的初始化代码,是否注意到什么特殊之处?您应该注意到自动生成了构造函数。








类初始化

Groovy 自动提供一个构造函数,构造函数接受一个名称-值对的映射,这些名称-值对与类的属性相对应。这是 Groovy 的一项开箱即用的功能 — 用于类中定义的任何属性,Groovy 允许将存储了大量值的映射传给构造函数。映射的这种用法很有意义,例如,您不用初始化对象的每个属性。

也可以添加下面这样的代码:

def sng2 = new Song(name:"Kung Fu Fighting", genre:"Disco")

也可以像下面这样直接操纵类的属性:

def sng3 = new Song()
sng3.name = "Funkytown"
sng3.artist = "Lipps Inc."
sng3.setGenre("Disco")

assert sng3.getArtist() == "Lipps Inc."

从这个代码中明显可以看出,Groovy 不仅创建了一个构造函数,允许传入属性及其值的映射,还可以通过 . 语法间接地访问属性。而且,Groovy 还生成了标准的 setter 和 getter 方法。

在进行属性操纵时,非常有 Groovy 特色的是:总是会调用 setter 和 getter 方法 — 即使直接通过 . 语法访问属性也是如此。








核心的灵活性

Groovy 是一种本质上就很灵活的语言。例如,看看从前面的代码中将 setGenre() 方法调用的括号删除之后会怎么样,如下所示:

sng3.setGenre "Disco"
assert sng3.genre == "Disco"

在 Groovy 中,对于接受参数的方法,可以省略括号 — 在某些方面,这样做会让代码更容易阅读。







方法覆盖

迄今为止已经成功地创建了 Song 类的一些实例。但是,它们还没有做什么有趣的事情。可以用以下命令输出一个实例:

println sng3 

在 Java 中这样只会输出所有对象的默认 toString() 实现,也就是类名和它的 hashcode(即 org.acme.groovy.Song@44f787)。下面来看看如何覆盖默认的 toString() 实现,让输出效果更好。

Song 类中,添加以下代码:

String toString(){
      
"${name}, ${artist}, ${genre}"
}

根据本教程已经学到的内容,可以省略 toString() 方法上的 public 修改符。仍然需要指定返回类型(String),以便实际地覆盖正确的方法。方法体的定义很简洁 — 但 return 语句在哪?







不需要 return

您可能已经想到:在 Groovy 中可以省略 return 语句。Groovy 默认返回方法的最后一行。所以在这个示例中,返回包含类属性的 String

重新运行 SongExample 类,应该会看到更有趣的内容。toString() 方法返回一个描述,而不是 hashcode。







特殊访问

Groovy 的自动生成功能对于一些功能来说很方便,但有些时候需要覆盖默认的行为。例如,假设需要覆盖 Song 类中 getGenre() 方法,让返回的 String 全部为大写形式。

提供这个新行为很容易,只要定义 getGenre() 方法即可。可以让方法的声明返回 String,也可以完全省略它(如果愿意)。下面的操作可能是最简单的:

def getGenre(){
      
genre.toUpperCase()
}

同以前一样,这个简单方法省略了返回类型和 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")
println sng4







就是 Java

您将会注意到,虽然预期可能有异常,但是没有生成异常。即使没有定义 genregetGenre() 方法也会调用 toUpperCase()

您还记得 Groovy 就是 Java,对吧?所以在 SongtoString() 中,引用了 genre 属性本身,所以不会调用 getGenre()。现在更改 toString() 方法以使用 getGenre(),然后再看看程序运行的结果。

String toString(){
      
"${name}, ${artist}, ${getGenre()}"
}

重新运行 SongExample,出现类似的异常。现在,请自己尝试修复这个问题,看看会发生什么。








另一个方便的小操作符

希望您做的修改与我的类似。在下面将会看到,我进一步扩充了 Song 类的 getGenre() 方法,以利用 Groovy 中方便的 ? 操作符。

def getGenre(){
      
genre?.toUpperCase()
}

? 操作符时刻都非常有用,可以极大地减少条件语句。