q代是编E的基础。您l常会遇到需要进行逐项遍历的内容,比如 List
?code>File ?JDBC ResultSet
。Java 语言几乎L提供了某U方法帮助您逐项遍历所需的内容,但o人沮丧的是,它ƈ没有l出一U标准方法。Groovy 的P代方法非常实用,在这一点上QGroovy ~程?Java ~程截然不同。通过一些代码示例,本文介l?Groovy 的万能的 each()
ҎQ从而将 Java 语言的那些P代怪癖抛在脑后?
假设您有一?Java ~程语言?java.util.List
。清?1 展示了在 Java 语言中如何用编E实现P代:
import java.util.*; |
׃提供了大部分集合c都可以׃n?java.lang.Iterable
接口Q您可以使用相同的方法遍?java.util.Set
?java.util.Queue
?
Groovy 是一ƾ运行在 Java q_之上的现代编E语a。它能够与现?Java 代码无缝集成Q同时引入了闭包和元~程{出色的新特性。简而言之,Groovy cM?21 世纪?Java 语言?
如果要将新工具集成到开发工L中,最关键的是理解什么时候需要用它以及什么时候不适合使用它。Groovy 可以变得非常强大Q但前提是它被适当地应用到合适的场景中。因此, 实战 Groovy pd旨在展示 Groovy 的实际用,以及何时和如何成功应用它?
现在Q假设该语言存储?java.util.Map
中。在~译Ӟ试?Map
获取 Iterator
会导致失?— Map
q没有实?Iterable
接口。幸q的是,可以调用 map.keySet()
q回一?Set
Q然后就可以l箋处理。这些小差异可能会媄响您的速度Q但不会妨碍您的前进。需要注意的是,List
?code>Set ?Queue
实现?Iterable
Q但?Map
没有 — 即它们位于相同?java.util
包中?
现在假设该语a存在?String
数组中。数l是一U数据结构,而不是类。不能对 String
数组调用 .iterator()
Q因此必M用稍微不同的q代{略。您再一ơ受到阻,但可以用如清单 2 所C的Ҏ解决问题Q?
public class ArrayTest{ |
但是{一?— 使用 Java 5 引入?for-each 语法怎么P它可以处理Q何实?Iterable
的类和数l,如清?3 所C:
import java.util.*; |
因此Q您可以使用相同的方法遍历数l和集合Q?code>Map 除外Q。但是如果语a存储?java.io.File
Q那该怎么办?如果存储?JDBC ResultSet
Q或者存储在 XML 文档?code>java.util.StringTokenizer 中呢Q面Ҏ一U情况,必须使用一U稍有不同的q代{略。这样做q不是有什么特D目?— 而是因ؓ不同?API 是由不同的开发h员在不同的时期开发的 — 但事实是Q您必须了解 6 ?Java q代{略Q特别是使用q些{略的特D情c?
Eric S. Raymond 在他?The Art of Unix Programming 一书中解释? “最意外原?#8221;。他写道Q?#8220;要设计可用的接口Q最好不要设计全新的接口模型。新鲜的东西L难以入门Q会为用户带来学习的负担Q因此应当尽量减新?宏V?#8221;Groovy 对P代的态度正是采纳?Raymond 的观炏V在 Groovy 中遍历几乎Q何结构时Q您只需要?each()
q一U方法?
首先Q我?清单 3 中的 List
重构?Groovy。在q里Q只需要直接对列表调用 each()
Ҏq传递一个闭包,而不是将 List
转换?for
循环Q顺便提一句,q样做ƈ不是特别h面向对象的特征,不是吗)?
创徏一个名?listTest.groovy 的文件ƈd清单 4 中的代码Q?/p>
清单 4. Groovy 列表q代
def list = ["Java", "Groovy", "JavaScript"] list.each{language-> println language } |
清单 4 中的W一行是 Groovy 用于构徏 java.util.ArrayList
的便捯法。可以将 println list.class
d到此脚本来验证这一炏V接下来Q只需对列表调?each()
Qƈ在闭包体内输?language
变量。在闭包的开始处使用 language->
语句命名 language
变量。如果没有提供变量名QGroovy 提供了一个默认名U?it
。在命o行提C符中输?groovy listTest
q行 listTest.groovy?/p>
清单 5 是经q简化的 清单 4 代码版本Q?/p>
清单 5. 使用 Groovy ?it
变量的P?/strong>
// shorter, using the default it variable |
Groovy 允许您对数组?List
交替使用 each()
Ҏ。ؓ了将 ArrayList
改ؓ String
数组Q必d as String[]
d到行末,如清?6 所C:
def list = ["Java", "Groovy", "JavaScript"] as String[] list.each{println it} |
?Groovy 中普遍?each()
ҎQƈ?getter 语法非常便捷Q?code>getClass() ?class
是相同的调用Q,q您能够编写既z又富有表达性的代码。例如,假设您希望利用反显C给定类的所有公共方法。清?7 展示了这个例子:
def s = "Hello World" |
脚本的最后一行调?getClass()
Ҏ?code>java.lang.Class 提供了一?getMethods()
ҎQ后者返回一个数l。通过这些操作串qv来ƈ?Method
的结果数l调?each()
Q您只用了一行代码就完成了大量工作?
但是Q与 Java for-each 语句不同的是Q万能的 each()
Ҏq不仅限?List
和数l。在 Java 语言中,故事到此l束。然而,?Groovy 中,故事才刚刚开始?
从前文可以看刎ͼ?Java 语言中,无法直接q代 Map
。在 Groovy 中,q完全不是问题,如清?8 所C:
def map = ["Java":"server", "Groovy":"server", "JavaScript":"web"] map.each{ println it } |
要处理名U?值对Q可以用隐式的 getKey()
?getValue()
ҎQ或在包的开头部分显式地命名变量Q如清单 9 所C:
def map = ["Java":"server", "Groovy":"server", "JavaScript":"web"] |
可以看到QP?Map
和P代其它Q何集合一栯然?
在l研I下一个P代例子前Q应当了?Groovy 中有?Map
的另一个语法。与?Java 语言中调?map.get("Java")
不一P可以化对 map.Java
的调用,如清?10 所C:
def map = ["Java":"server", "Groovy":"server", "JavaScript":"web"] |
不可否认QGroovy 针对 Map
的这U便捯法非帔RQ但q也是在?Map
使用反射时引起一些常见问题的原因。对 list.class
的调用将生成 java.util.ArrayList
Q而调?map.class
q回 null
。这是因?map 元素的便h法覆盖了实际?getter 调用?code>Map 中的元素都不h class
键,因此调用实际会返?null
Q如清单 11 的示例所C:
null
def list = ["Java", "Groovy", "JavaScript"] |
q是 Groovy 比较|见的打?“最意外原?#8221; 的情况,但是׃?map 获取元素要比使用反射更加常见Q因此我可以接受q一例外?/p>
现在您已l熟?each()
Ҏ了,它可以出现在所有相关的位置。假设您希望q代一?String
Qƈ且是逐一q代字符Q那么马上可以?each()
Ҏ。如清单 12 所C:
String
q代def name = "Jane Smith" name.each{letter-> |
q提供了所有的可能性,比如使用下划U替代所有空|如清?13 所C:
|
当然Q在替换一个单个字母时QGroovy 提供了一个更加简z的替换Ҏ。您可以清?13 中的所有代码合qؓ一行代码:"Jane Smith".replace(" ", "_")
。但是对于更复杂?String
操作Q?code>each() Ҏ是最佳选择?
Groovy 提供了原生的 Range
cdQ可以直接P代。用两个点分隔的所有内容(比如 1..10
Q都是一?Range
。清?14 展示了这个例子:
def range = 5..10 range.each{ |
Range
不局限于单的 Integer
。考虑清单 15 在的代码Q其中P?Date
?Range
Q?
Date
q代def today = new Date() |
可以看到Q?code>each() 准确地出现在您所期望的位|。Java 语言~Z原生?Range
cdQ但是提供了一个类似地概念Q采?enum
的Ş式。毫不奇怪,在这?each()
仍然z־上用场?/p>
Java enum
是按照特定顺序保存的随意的值集合。清?16 展示?each()
Ҏ如何自然地配?enum
Q就好象它在处理 Range
操作W一P
enum
q代enum DAY{ |
?Groovy 中,有些情况下,each()
q个名称q未能表辑֮的强大功能。在下面的例子中Q将看到使用特定于所用上下文的方法对 each()
Ҏq行修饰。Groovy eachRow()
Ҏ是一个很好的例子?
在处理关pL据库表时Q经怼?“我需要针对表中的每一行执行操?#8221;。比较一下前面的例子。您很可能会?“我需要对列表中的每一U语a执行一些操?#8221;。根据这个道理,groovy.sql.Sql
对象提供了一?eachRow()
ҎQ如清单 17 所C:
ResultSet
q代import groovy.sql.* |
该脚本的W一行代码实例化了一个新?Sql
对象Q设|?JDBC q接字符丌Ӏ用户名、密码和 JDBC 驱动器类。这Ӟ可以调用 eachRow()
ҎQ传?SQL select
语句作ؓ一个方法参数。在闭包内部Q可以引用列名(name
?code>version?code>urlQ,好像实际存?getName()
?code>getVersion() ?getUrl()
Ҏ一栗?
q显然要?Java 语言中的{效Ҏ更加清晰。在 Java 中,必须创徏单独?DriverManager
?code>Connection?code>Statement ?JDBCResultSet
Q然后必d嵌套?try
/catch
/finally
块中它们全部清除?
对于 Sql
对象Q您会认?each()
?eachRow()
都是一个合理的Ҏ名。但是在接下来的CZ中,我想您会认ؓ each()
q个名称q不能充分表辑֮的功能?
我从未想q用原始的 Java 代码逐行遍历 java.io.File
。当我完成了所有的嵌套?BufferedReader
?FileReader
后(更别提每个流E末所有异常处理)Q我已经忘记最初的目的是什么?
清单 18 展示了?Java 语言完成的整个过E:
import java.io.BufferedReader; |
清单 19 展示?Groovy 中的{效q程Q?
def f = new File("languages.txt") f.eachLine{language-> |
q正?Groovy 的简z性真正擅长的斚w。现在,我希望您了解Z么我?Groovy UCؓ “Java E序员的 DSL”?
注意Q我?Groovy ?Java 语言中同时处理同一?java.io.File
cR如果该文g不存在,那么 Groovy 代码抛出和 Java 代码相同?FileNotFoundException
异常。区别在于,Groovy 没有已检的异常。在 try
/catch
/finally
块中装 eachLine()
l构是我自己的爱?— 而不是一语a需求。对于一个简单的命o行脚本中Q我ƣ赏 清单 19 中的代码的简z性。如果我在运行应用服务的同时执行相同的P代,我不能对q些异常坐视不管。我在?Java 版本相同?try/catch
块中装 eachLine()
块?
File
cd each()
Ҏq行了一些修攏V其中之一是 splitEachLine(String separator, Closure closure)
。这意味着您不仅可以逐行遍历文gQ同时还可以它分ؓ不同的标记。清?20 展示了一个例子:
// languages.txt |
如果处理的是二进制文ӞGroovy q提供了一?eachByte()
Ҏ?
当然QJava 语言中的 File
q不L一个文?— 有时是一个目录。Groovy q提供了一?each()
修改以处理子目录?
使用 Groovy 代替 shell 脚本Q或批处理脚本)非常ҎQ因为您能够方便地访问文件系l。要获得当前目录的目录列表,参见清单 21Q?/p>
清单 21. 目录q代
def dir = new File(".") dir.eachFile{file-> |
eachFile()
Ҏ同时q回了文件和子目录。?Java 语言?isFile()
?isDirectory()
ҎQ可以完成更复杂的事情。清?22 展示了一个例子:
def dir = new File(".") dir.eachFile{file-> |
׃两种 Java Ҏ都返?boolean
|可以在代码中d一?Java 三元操作W。清?23 展示了一个例子:
def dir = new File(".") |
如果只对目录有兴,那么可以使用 eachDir()
而不?eachFile()
。还提供?eachDirMatch()
?eachDirRecurse()
Ҏ?
可以看到Q对 File
仅?each()
Ҏq不能提供够的含义。典?each()
Ҏ的语义保存在 File
中,但是Ҏ名更h描述性,从而提供更多有兌个高U功能的信息?
理解了如何遍?File
后,可以使用相同的原则遍?HTTP h的响应。Groovy ?java.net.URL
提供了一个方便的Q和熟悉的)eachLine()
Ҏ?
例如Q清?24 逐行遍历 ibm.com 主页?HTMLQ?
def url = new URL("http://www.ibm.com") |
当然Q如果这是您的目的的话QGroovy 提供了一个只包含一行代码的解决办法Q这主要归功?toURL()
ҎQ它被添加到所?Strings
Q?code>"http://www.ibm.com".toURL().eachLine{ println it }?
但是Q如果希望对 HTTP 响应执行一些更有用的操作,该怎么办呢Q具体来Ԍ如果发出的请求指向一?RESTful Web 服务Q而该服务包含您要解析?XMLQ该怎么做呢Q?code>each() Ҏ在q种情况下提供帮助?
您已l了解了如何Ҏ件和 URL 使用 eachLine()
Ҏ。XML l出了一个稍微有些不同的问题 — 与逐行遍历 XML 文档相比Q您可能更希望对逐个元素q行遍历?
例如Q假设您的语a列表存储在名?languages.xml 的文件中Q如清单 25 所C:
<langs> |
Groovy 提供了一?each()
ҎQ但是需要做一些修攏V如果用名?XmlSlurper
的原?Groovy c解?XMLQ那么可以?each()
遍历元素。参见清?26 所C的例子Q?
def langs = new XmlSlurper().parse("languages.xml") |
langs.language.each
语句从名?<language>
?<langs>
提取所有元素。如果同时拥?<format>
?<server>
元素Q它们将不会出现?each()
Ҏ的输Z?/p>
如果觉得q还不够的话Q那么假设这?XML 是通过一?RESTful Web 服务的Ş式获得,而不是文件系l中的文件。用一?URL 替换文g的\径,其余代码仍然保持不变Q如清单 27 所C:
def langs = new XmlSlurper().parse("http://somewhere.com/languages") |
q真是个好方法,each()
Ҏ在这里用得很好,不是吗?
在?each()
Ҏ的整个过E中Q最妙的部分在于它只需要很的工作可以处理大?Groovy 内容。解?each()
Ҏ之后QGroovy 中的q代易如反掌了。正?Raymond 所_q正是关键所在。一旦了解了如何遍历 List
Q那么很快就会掌握如何遍历数l?code>Map?code>String?code>Range?code>enum、SQL ResultSet
?code>File、目录和 URL
Q甚x XML 文档的元素?
本文的最后一个示例简单提C?XmlSlurper
实现 XML 解析。在下一期文章中Q我l讨个问题,q展CZ?Groovy q行 XML 解析有多么简单!您将看到 XmlParser
?XmlSlurper
的实际用,q更好地了解 Groovy Z么提供两个类g又略有不同的cd?XML 解析。到那时Q希望您能发?Groovy 的更多实际应用?