`
aihhd2008
  • 浏览: 24343 次
  • 性别: Icon_minigender_1
  • 来自: 合肥
最近访客 更多访客>>
社区版块
存档分类
最新评论

Groovy 基础知识入门

阅读更多
用 Groovy 编写的 Hello World

就像前面提到过的,Groovy 支持松散的 Java 语法 — 例如,不需要为打印 “Hello World!” 这样的简单操作定义类。
而且,Groovy 使日常的编码活动变得更容易,例如,Groovy 允许输入 println,而无需输入 System.out.println。当您输入 println 时,Groovy 会非常聪明地知道您指的是 System.out。

所以,用 Groovy 编写 Hello World 程序就如下面这样简单:

println "Hello World!"

运行 Groovy 示例
假设我将代码保存在文件 MyFirstExample.groovy 内,只要输入以下代码就能运行这个示例:

c:>groovy MyFirstExample.groovy


在控制台上输出 “Hello World!” 所需的工作就这么多。

Groovy 允许完全省略编译步骤,不过仍然可以 进行编译。如果想要编译代码,可以使用 Groovy 编译器 groovyc。用 groovyc 编译 Groovy 代码会产生标准的 Java 字节码,然后可以通过 java 命令运行生成的字节码。这是 Groovy 的一项经常被忽略的关键特性:用 Groovy 编写的所有代码都能够通过标准 Java 运行时编译和运行。
至于运行代码,如果我希望更加简洁,我甚至还能输入

c:>groovy -e "println 'Hello World!'"


这会生成相同的结果,而且甚至无需定义任何文件!


Groovy 是没有类型的 Java 代码

为什么要有类型定义?
在 Java 中,如果要声明一个 String 变量,则必须输入:

String value = "Hello World";


但是,如果仔细想想,就会看出,等号右侧的字符已经表明 value 的类型是 String。所以,Groovy 允许省略 value 前面的 String 类型变量,并用 def 代替。

def value = "Hello World"


实际上,Groovy 会根据对象的值来判断它的类型。

运行程序!
将 HelloWorld.groovy 文件中的代码编辑成下面这样:

String message = "Hello World"
println message


运行这段代码,应该会在控制台上看到与前面一样的 “Hello World”。现在,将变量类型 String 替换为 def 并重新运行代码。是不是注意到了相同的结果?
除了输出 message 的值,还可以用以下调用输出它的类型:

def message = "Hello World"
println message.class

输出 “class java.lang.String” 应该是目前为止很受欢迎的一项变化!如果想知道到底发生了什么,那么可以告诉您:Groovy 推断出 message 一定是 String 类型的,因为它的值是用双引号括起来的。

类型推断的更多内容
您可能听说过,在 Groovy 中 “一切都是对象” — 但对于类型来说这句话意味着什么呢?让我们看看如果将前面示例中的 String 替换为数字会怎么样,如下所示:

def message = 12
println message.class


message 变量的数字值看起来像是 Java 的原生类型 int。但是,运行这个代码就可以看出,Groovy 将它作为 Integer。这是因为在 Groovy 中 “一切都是对象”。
Java 中的所有对象都扩展自 java.lang.Object,这对 Groovy 来说非常方便。即使在最糟的情况下,Groovy 运行时不能确定变量的类型,它只需将变量当成 Object,问题就解决了。
继续使用这段代码。将 message 改成自己喜欢的任意类型:Groovy 会在运行时尽其所能推断出这个变量的类型。

无类型有什么意义
那么,Groovy 缺少类型意味着所需的输入更少。不可否认,将 String 替换成 def 并没有真正节约多少打字工作 — 三个字母并不值得如何夸耀!但是在更高的层次上看,在编写大量不仅仅包含变量声明的代码的时候,没有类型确实减少了许多打字工作。更重要的是,这意味着要阅读的代码要少得多。最后,Groovy 缺少类型能够带来更高的灵活性 — 不需要接口或抽象类。
所以,只需要使用 def 关键字就能在方法中声明一个独立变量,不需要将 def 关键字作为方法声明中的参数。在 for 循环声明中也不需要它,这意味着不用编写 (int x = 0; x < 5; x++),相反,可以省略 int,保留空白。


更好、更短的循环
下面这种方法可以更好地感受 Groovy 缺乏类型的好处:首先,用与创建 HelloWorld 相同的方式创建一个 Groovy 类,将这个类称为 MethodMadness,并删除自动生成的类体:将要定义一个独立的 repeat函数。现在在控制台中输入以下代码:

def repeat(val){
 for(i = 0; i < 5; i++){
  println val
 }
}

起初,从 Java 的角度来看,这个小函数看起来可能有些怪(实际上,它很像 JavaScript)。但它就是 Java 代码,只不过是用 Groovy 的样式编写的。
深入方法
repeat 函数接受一个变量 val。请注意参数不需要 def。方法体本质上就是一个 for 循环。调用这个函数。

repeat("hello  world")


会输出 “hello world” 五次。请注意,for 循环中省略了 int。没有变量类型的 for 循环要比标准的 Java 代码短些。现在看看如果在代码里加入范围会出现什么情况。

Groovy 中的范围
范围 是一系列的值。例如 “0..4” 表明包含 整数 0、1、2、3、4。Groovy 还支持排除范围,“0..<4” 表示 0、1、2、3。还可以创建字符范围:“a..e” 相当于 a、b、c、d、e。“a..<e” 包括小于 e 的所有值。

循环范围
范围为循环带来了很大的方便。例如,前面从 0 递增到 4 的 for 循环如下所示:
for(i = 0; i < 5; i++)

范围可以将这个 for 循环变得更简洁,更易阅读:

def repeat(val){
 for(i in 0..5){
  println val
 }
}

设置范围
如果运行这个示例,可能会注意到一个小问题:“Hello World” 输出了六次而不是五次。这个问题有三种解决方法:
• 将包含的范围限制到 4:

for(i in 0..4)

	• 从 1 而不是 0 开始:
	
	def repeat(val){
 for(i in 1..5){
  println val
 }
}
	

         将范围由包含改为排除:

def repeat(val){
 for(i in 0..<5){
  println val
 }
}


不论采用哪种方法,都会得到原来的效果 — 输出 “Hello World” 五次。

默认参数值
现在已经成功地使用 Groovy 的范围表达式缩短了 repeat 函数。但这个函数依然有些限制。如果想重复 “Hello World” 八次该怎么办?如果想对不同的值重复不同次数 — 比如 “Hello World” 重复八次,“Goodbye Sunshine” 重复两次,这时该怎么办?
每次调用 repeat 时都要指定需要的重复次数的做法已经过时了,特别是在已经适应了默认行为(重复五次)的时候。
Groovy 支持默认参数值,可以在函数或方法的正式定义中指定参数的默认值。调用函数的程序可以选择省略参数,使用默认值。
更加复杂的参数值
使用前面的 repeat 函数时,如果希望调用程序能够指定重复值,可以像下面这样编码:

def repeat(val, repeat=5){
 for(i in 0..<repeat){
  println val
 }
}


像下面这样调用该函数:

repeat("Hello World", 2)
repeat("Goodbye sunshine", 4)
repeat("foo")

结果会输出 “Hello World” 两次,“Goodbye sunshine” 四次,“foo” 五次(默认次数)。

可以将范围当作集合
在前一节学习了如何用 Groovy 的范围将循环变得更容易。范围表达式 “0..4” 代表数字的集合 — 0、1、2、3 和 4。为了验证这一点,请创建一个新类,将其命名为 Ranger。保留类定义和 main 方法定义。但是这次添加以下代码:
def range = 0..4
println range.class
assert range instanceof List


请注意,assert 命令用来证明范围是 java.util.List 的实例。接着运行这个代码,证实该范围现在是类型 List 的集合。

丰富的支持
Groovy 的集合支持相当丰富,而且美妙之处就在于,在 Groovy 的魔法背后,一切都是标准的 Java 对象。每个 Groovy 集合都是 java.util.Collection 或 java.util.Map 的实例。
前面提到过,Groovy 的语法提供了本地列表和映射。例如,请将以下两行代码添加到 Ranger 类中:

def coll = ["Groovy", "Java", "Ruby"]
assert  coll instanceof Collection
assert coll instanceof ArrayList

你将会注意到,coll 对象看起来很像 Java 语言中的数组。实际上,它是一个 Collection。要在普通的 Java 代码中得到集合的相同实例,必须执行以下操作:

Collection<String> coll = new ArrayList<String>();
coll.add("Groovy");
coll.add("Java");
coll.add("Ruby");

在 Java 代码中,必须使用 add() 方法向 ArrayList 实例添加项。

添加项
Groovy 提供了许多方法可以将项添加到列表 — 可以使用 add() 方法(因为底层的集合是一个普通的 ArrayList 类型),但是还有许多快捷方式可以使用。
例如,下面的每一行代码都会向底层集合加入一些项:

coll.add("Python")
coll << "Smalltalk"
coll[5] = "Perl"

请注意,Groovy 支持操作符重载 — << 操作符被重载,以支持向集合添加项。还可以通过位置参数直接添加项。在这个示例中,由于集合中只有四个项,所以 [5] 操作符将 “Perl” 放在最后。请自行输出这个集合并查看效果。

检索非常轻松
如果需要从集合中得到某个特定项,可以通过像上面那样的位置参数获取项。例如,如果想得到第二个项 “Java”,可以编写下面这样的代码(请记住集合和数组都是从 0 开始):
assert coll[1] == "Java"

Groovy 还允许在集合中增加或去掉集合,如下所示:

def numbers = [1,2,3,4]
assert numbers + 5 == [1,2,3,4,5]
assert numbers - [2,3] == [1,4]


请注意,在上面的代码中, 实际上创建了新的 集合实例,由最后一行可以看出。


魔法方法
Groovy 还为集合添加了其他一些方便的功能。例如,可以在集合实例上调用特殊的方法,如下所示:

def numbers = [1,2,3,4]
assert numbers.join(",") == "1,2,3,4" 
assert [1,2,3,4,3].count(3) == 2


join() 和 count() 只是在任何项列表上都可以调用的众多方便方法中的两个。分布操作符(spread operator) 是个特别方便的工具,使用这个工具不用在集合上迭代,就能够调用集合的每个项上的方法。
假设有一个 String 列表,现在想将列表中的项目全部变成大写,可以编写以下代码:

assert ["JAVA", "GROOVY"] == ["Java", "Groovy"]*.toUpperCase()

请注意 *. 标记。对于以上列表中的每个值,都会调用 toUpperCase(),生成的集合中每个 String 实例都是大写的。

Groovy 映射
除了丰富的列表处理功能,Groovy 还提供了坚固的映射机制。同列表一样,映射也是本地数据结构。而且 Groovy 中的任何映射机制在幕后都是 java.util.Map 的实例。
Java 语言中的映射
Java 语言中的映射是名称-值对的集合。所以,要用 Java 代码创建典型的映射,必须像下面这样操作:

Map<String, String>map = new HashMap<String, String>();
map.put("name", "Andy");
map.put("VPN-#","45");


一个 HashMap 实例容纳两个名称-值对,每一个都是 String 的实例。

通过 Groovy 进行映射
Groovy 使得处理映射的操作像处理列表一样简单 — 例如,可以用 Groovy 将上面的 Java 映射写成

def hash = [name:"Andy", "VPN-#":45]


请注意,Groovy 映射中的键不必是 String。在这个示例中,name 看起来像一个变量,但是在幕后,Groovy 会将它变成 String。

全都是 Java
接下来创建一个新类 Mapper 并加入上面的代码。然后添加以下代码,以证实底层的代码是真正的 Java 代码:

assert hash.getClass() == java.util.LinkedHashMap


可以看到 Groovy 使用了 Java 的 LinkedHashMap 类型,这意味着可以使用标准的 Java 一样语句对 hash 中的项执行 put 和 get 操作。

hash.put("id", 23)
assert hash.get("name") == "Andy"


有 groovy 特色的映射
现在您已经看到,Groovy 给任何语句都施加了魔法,所以可以用 . 符号将项放入映射中。如果想将新的名称-值对加入映射(例如 dob 和 “01/29/76”),可以像下面这样操作:

hash.dob = "01/29/76"


. 符号还可以用来获取项。例如,使用以下方法可以获取 dob 的值:

assert hash.dob == "01/29/76"


当然 . 要比调用 get() 方法更具 Groovy 特色。


位置映射
还可以使用假的位置语法将项放入映射,或者从映射获取项目,如下所示:

assert hash["name"] == "Andy"
hash["gender"] = "male"
assert hash.gender == "male"
assert hash["gender"] == "male"

但是,请注意,在使用 [] 语法从映射获取项时,必须将项作为 String 引用。


Groovy 中的闭包

虽然在前几节编写了不少集合代码,但还没有实际地在集合上迭代。当然,您知道 Groovy 就是 Java,所以如果愿意,那么总是能够得到 Java 的 Iterator 实例,用它在集合上迭代,就像下面这样:

def acoll = ["Groovy", "Java", "Ruby"]
		
for(Iterator iter = acoll.iterator(); iter.hasNext();){
 println iter.next()
}


实际上在 for 循环中并不需要类型声明,因为 Groovy 已经将迭代转变为任何集合的直接成员。在这个示例中,不必获取 Iterator 实例并直接操纵它,可以直接在集合上迭代。而且,通常放在循环构造内的行为(例如 for 循环体中println)接下来要放在闭包内。在深入之前,先看看如何执行这步操作。

能否看见闭包?
对于上面的代码,可以用更简洁的方式对集合进行迭代,如下所示:

def acoll = ["Groovy", "Java", "Ruby"]
		
acoll.each {
 println it
}


请注意,each 直接在 acoll 实例内调用,而 acoll 实例的类型是 ArrayList。在 each 调用之后,引入了一种新的语法 — {,然后是一些代码,然后是 }。由 {} 包围起来的代码块就是闭包。
执行代码
闭包是可执行的代码块。它们不需要名称,可以在定义之后执行。所以,在上面的示例中,包含输出 it(后面将简单解释 it)的行为的无名闭包将会在 acoll 集合类型中的每个值上被调用。
在较高层面上,{} 中的代码会执行三次。
闭包中的 it 变量是一个关键字,指向被调用的外部集合的每个值 — 它是默认值,可以用传递给闭包的参数覆盖它。下面的代码执行同样的操作,但使用自己的项变量:

def acoll = ["Groovy", "Java", "Ruby"]
		
acoll.each{ value ->
 println value
}


在这个示例中,用 value 代替了 Groovy 的默认it。

迭代无处不在
闭包在 Groovy 中频繁出现,但是,通常用于在一系列值上迭代的时候。请记住,一系列值可以用多种方式表示,不仅可以用列表表示 — 例如,可以在映射、String、JDBC Rowset、File 的行上迭代,等等。
如果想在前面一节 “Groovy 中的映射” 中的 hash 对象上迭代,可以编写以下代码:

def hash = [name:"Andy", "VPN-#":45]
hash.each{ key, value ->
 println "${key} : ${value}"
}


请注意,闭包还允许使用多个参数 — 在这个示例中,上面的代码包含两个参数(key 和 value)。

使用 Java 代码迭代
以下是使用典型的 Java 构造如何进行同样的迭代:

Map<String, String>map = new HashMap<String, String>();
map.put("name", "Andy");
map.put("VPN-#","45");
		
		
for(Iterator iter = map.entrySet().iterator(); iter.hasNext();){
 Map.Entry entry = (Map.Entry)iter.next();
 System.out.println(entry.getKey() + " : " + entry.getValue());
}

上面的代码比 Groovy 的代码长得多,是不是?如果要处理大量集合,那么显然用 Groovy 处理会更方便。



迭代总结
请记住,凡是集合或一系列的内容,都可以使用下面这样的代码进行迭代。

"ITERATION".each{
 println it.toLowerCase()
}



闭包的更多使用方式
虽然在迭代上使用闭包的机会最多,但闭包确实还有其他用途。因为闭包是一个代码块,所以能够作为参数进行传递(Groovy 中的函数或方法不能这样做)。闭包在调用的时候才会执行这一事实(不是在定义的时候)使得它们在某些场合上特别有用。
例如,通过 Eclipse 创建一个 ClosureExample 对象,并保持它提供的默认类语法。在生成的 main() 方法中,添加以下代码:

def excite = { word ->
 return "${word}!!"
}

这段代码是名为 excite 的闭包。这个闭包接受一个参数(名为 word),返回的 String 是 word 变量加两个感叹号。请注意在 String 实例中替换 的用法。在 String 中使用 ${value}语法将告诉 Groovy 替换 String 中的某个变量的值。可以将这个语法当成 return word + "!!" 的快捷方式。

延迟执行
既然有了闭包,下面就该实际使用它了。可以通过两种方法调用闭包:直接调用或者通过 call() 方法调用。
继续使用 ClosureExample 类,在闭包定义下面添加以下两行代码:

assert "Groovy!!" == excite("Groovy")
assert "Java!!" == excite.call("Java")


Song 类
我们先从用 Groovy 定义一个简单的 JavaBean 形式的类开始,这个类称为 Song。
第一步自然是用 Groovy 创建名为 Song 的类。这次还要为它创建一个包结构 — 创建一个包名,例如 org.acme.groovy。
创建这个类之后,删除 Groovy 插件自动生成的 main()。
歌曲有一些属性 — 创作歌曲的艺术家、歌曲名称、风格等等。请将这些属性加入新建的 Song 类,如下所示:

package org.acme.groovy

class Song {
 def name
 def artist
 def genre
}


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 类的时候了。首先导入实例,并将下面的代码添加到 SongExample 的 main() 方法中。

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}"
}

不需要 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
您将会注意到,虽然预期可能有异常,但是没有生成异常。即使没有定义 genre,getGenre() 方法也会调用 toUpperCase()。
您还记得 Groovy 就是 Java,对吧?所以在 Song 的 toString() 中,引用了 genre 属性本身,所以不会调用 getGenre()。现在更改 toString() 方法以使用 getGenre(),然后再看看程序运行的结果

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


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


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

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


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







































































0
0
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics