Groovy 集合与闭包

御弟哥哥
• 阅读 1543

Groovy 集合

在 Groovy 提供的所有方便的快捷方式和功能中,最有帮助的一个可能就是内置的 集合。回想一下在 Java 编程中是如何使用集合的 — 导入 java.util 类,初始化集合,将项加入集合。这三个步骤都会增加不少代码。

而 Groovy 可以直接在语言内使用集合。在 Groovy 中,不需要导入专门的类,也不需要初始化对象。集合是语言本身的本地成员。Groovy 也使集合(或者列表)的操作变得非常容易,为增加和删除项提供了直观的帮助。

可以将范围当作集合

在前一节学习了如何用 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 中的闭包

现在,闭包是 Java 世界的一个重大主题,对于是否会在 Java 7 中包含闭包仍然存在热烈的争论。有些人会问:既然 Groovy 中已经存在闭包,为什么 Java 语言中还需要闭包?这一节将学习 Groovy 中的闭包。如果没有意外,在闭包成为 Java 语法的正式部分之后,这里学到的内容将给您带来方便。

不再需要更多迭代

虽然在前几节编写了不少集合代码,但还没有实际地在集合上迭代。当然,您知道 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 集合类型中的每个值上被调用。

在较高层面上,{} 中的代码会执行三次,从而生成如图 13 所示的输出。

图 13. 迭代从未像现在这样容易 Groovy 集合与闭包

闭包中的 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")

可以看到,两种调用方式都能工作,但是直接调用的方法更简洁。不要忘记闭包在 Groovy 中也是一类对象 — 既可以作为参数传递,也可以放在以后执行。用普通的 Java 代码可以复制同样的行为,但是不太容易。现在不会感到惊讶了吧?

点赞
收藏
评论区
推荐文章
blmius blmius
2年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
Jacquelyn38 Jacquelyn38
2年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
Stella981 Stella981
2年前
Android So动态加载 优雅实现与原理分析
背景:漫品Android客户端集成适配转换功能(基于目标识别(So库35M)和人脸识别库(5M)),导致apk体积50M左右,为优化客户端体验,决定实现So文件动态加载.!(https://oscimg.oschina.net/oscnet/00d1ff90e4b34869664fef59e3ec3fdd20b.png)点击上方“蓝字”关注我
Stella981 Stella981
2年前
GROOVY预览
1.Groovy中的闭包Java的一些不足可以通过使用groovy的闭包很好的解决,通过下面这个例子来看看使用闭包的优势:在Java中遍历一个集合的方法是使用迭代,就像下面这样:defacoll"Groovy","Java","Ruby"for(Iteratoriteracoll.iterator()
Wesley13 Wesley13
2年前
35岁,真的是程序员的一道坎吗?
“程序员35岁是道坎”,“程序员35岁被裁”……这些话咱们可能都听腻了,但每当触及还是会感到丝丝焦虑,毕竟每个人都会到35岁。而国内互联网环境确实对35岁以上的程序员不太友好:薪资要得高,却不如年轻人加班猛;虽说经验丰富,但大部分公司并不需要太资深的程序员。但35岁危机并不是不可避免的,比如你可以不断精进技术,将来做技术管理或者
Wesley13 Wesley13
2年前
00:Java简单了解
浅谈Java之概述Java是SUN(StanfordUniversityNetwork),斯坦福大学网络公司)1995年推出的一门高级编程语言。Java是一种面向Internet的编程语言。随着Java技术在web方面的不断成熟,已经成为Web应用程序的首选开发语言。Java是简单易学,完全面向对象,安全可靠,与平台无关的编程语言。
Wesley13 Wesley13
2年前
35岁是技术人的天花板吗?
35岁是技术人的天花板吗?我非常不认同“35岁现象”,人类没有那么脆弱,人类的智力不会说是35岁之后就停止发展,更不是说35岁之后就没有机会了。马云35岁还在教书,任正非35岁还在工厂上班。为什么技术人员到35岁就应该退役了呢?所以35岁根本就不是一个问题,我今年已经37岁了,我发现我才刚刚找到自己的节奏,刚刚上路。
Wesley13 Wesley13
2年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
Python进阶者 Python进阶者
3个月前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这