Groovy – Java的脚本语言
by
Mark Volkmann, Partner
Object Computing, Inc. (OCI)
翻译:raxy
介绍
Groovy是用Java实现的开源脚本语言并且和它联系紧密.它需要JDK 1.4. Groovy向Java添加了许多Ruby和Python脚本语言的特性. Groovy的特性包括动态类型(dynamic typing), 闭包(closures),简单对象导航( easy object navigation)和更加简洁的Lists和Maps语法.所有这些特性和其他一些特性将会在本文中详细介绍.
这里引用Groovy网页上的话. "Groovy是设计用来以精简快速有趣的方式来在Java平台上处理事务,并且把类似Python Ruby等的强大功能带到Java的世界里."
Groovy脚本可以使用任何Java类.它们可以被编译成Java字节码 (即.class文件)并能被任何正常的Java类引用.Groovy编译器, groovyc,可以编译Groovy脚本文件和Java源文件,然而有些语法(例如内类)并不被支持.
理论上, 可以使用Groovy编写完整的应用程序,其具有和同等Java应用程序差不多的性能.这是Groovy和其他脚本语言不一样的如Ruby, Python, Perl以及BeanShell. 使Groovy现在运行得比Java还慢的原因之一是生成的字节码使用了映射(reflection)来调用构造函数以及私有/保护的方法.这个问题将会在以后的版本中解决.
Groovy是由James Strachan和Bob McWhirter创造的. James还参与了许多其他开源项目的开发,其中包括Jelly, dom4j, Jaxen, Betwixt和Maven. Bob是Jaxen和Drools (一个开源的面向对象的JAVA规则引擎) 的创始人.
本文并没有涉及Groovy所有的特性,而只是涉及了其中的大部分.它假设你对Java的语法有足够的了解并能把Java和Groovy的语法进行比较.
如果我们能在好的编程语言使用什么样的语法的问题上达成一致的话那么我们就不需要那么多语言了.这样大量的程序设计语言会被排除在外,显然我们并不同意.(Based on the number of programming languages out there, we obviously don't agree.)在读完本文之后,你可能认为Java的语法已经可以了并且认为Groovy的语法不太合你的口味.如果你的结论是这样,我鼓励你研究Pat Niemeyer 的BeanShell位于http://www.beanshell.org/.它更加接近于标准的Java语法.另一方面,如果你喜欢Groovy的简短的语法那么就一起groovy吧!
下载并安装Groovy
使用以下步骤可以下载Groovy.
1. 访问 http://groovy.codehaus.org/.
2. 点击下载顶部导航栏中的"Download".
3. 点击 "this site" 链接.
4. 选择一个版本下载.
在 CVS中可以下载最新的版本. 操作步骤的说明在这里可以找到.
要安装Groovy, 使用以下步骤.
1. 解压下载的文件.
2. 把环境变量GROOVY_HOME 设置到解压的目录.
3. 向环境变量PATH添加$GROOVY_HOME/bin (UNIX)或%GROOVY_HOME%\bin (Windows).
运行Groovy
运行Groovy脚本有四种方式.在这些方式中,脚本会被解析, 转换成Java源代码以及编译成Java字节码(bytecode).
互动的Shell
命令groovysh启动了一个互动的shell,可以在那里输入Groovy语句.输入一些语句,在每个语句结尾按回车(enter)键. 语句不会被求值(evaluated)或执行,直到输入execute命令为止.
互动的Swing Console
groovyConsole命令会启动一个Swing窗口.在窗口的下半部分输入Groovy语句.在Actions菜单中选择Run来运行他们.输出的内容在窗口的上半部分显示. 使用File 菜单中可以打开和保存脚本文件.
运行脚本文件
一个Groovy脚本文件 (扩展名为.groovy的文件)可以使用命令groovy script-name.groovy来运行.
运行编译好的脚本
使用命令 groovyc script-name.groovy,一个Groovy的脚本文件可以被编译为Java .class文件. 如果在脚本文件中使用了松散的语句(loose statements), 则.class文件中会包含一个main方法,这样它就可以象Java应用程序一样使用命令java script-name运行. Main方法的内容会在后面提到. classpath 必须包含Groovy lib目录中的groovy*.jar和asm*.jar文件.
有一个定制的Ant任务完成这个任务!这个类是org.codehaus.groovy.ant.Groovyc.
一些语法细节
以下是Java和Groovy语法关键的不同之处.其他的我们会在后面谈到.
· 它的目标是支持所有合法的Java语法, 但是这个目标暂时还未达到.
· 每行语句结尾的分号是可有可无的.
· 方法的参数旁边的圆括号也是可有可无的除非该方法没有参数或者是不加圆括号会造成歧义.但是,构造函数中的圆括号则是必需的.有些人更倾向于经常使用圆括号. 本文则倾向于在允许的情况下省略它.
· "return"语句在某些时候也是可有可无的.当一个方法要返回值的时候,如果执行到大括号(方法结尾的大括号)前的最后一条语句,那么就会把它的值作为返回值.以后Groovy可能会改成返回最后执行的那一条语句计算出来的值.
· Groovy 属性和方法默认的情况下是公开的(public),而不是象JAVA中是受保护的( protected). 稍后我们再讨论Groovy Beans.
· java.lang, groovy.lang和groovy.util类会被自动导入.
动态类型(Dynamic Typing)
类型对于变量,属性,方法/闭包的参数(method/closure parameters)以及方法的返回类型都是可有可无的.他们都是在赋值给他们的时候才决定类型. 不同的类型会在后面用到.任何类型都可以被使用,即使是基本类型 (通过自动包装(autoboxing)). 当需要时,很多类型之间的转换都会自动发生,比如在这些类型之间的转换: 字符串(String),基本类型(如int) 和类型的包装类(type wrapper classes) (如Integer)之间.我们也可以把不同的基本类型添加到同一数组(collections)中.
Added Methods
Groovy向标准Java类添加了许多方法例如java.lang.Object和 java.lang.String.可以在http://groovy.codehaus.org/groovy-jdk.html找到那些添加的方法.其他的我们以后再谈.
dump
该操作返回如下形式的字符串 <class-name@hashcode property-name=property-value ...> 例如, <Car@ef5502 make=Toyota model=Camry>.
Print与println
这两个静态方法打印对象的toString 方法的值.例如, print car或 println car.
invokeMethod
这个操作使用映射(reflection)实现动态方法调用.语法格式是object.invokeMethod(method-name, argument-array).下面的例子会打印数值4.
s = 'abcabc' // a java.lang.String
method = 'indexOf'
args = ['b', 2]
println s.invokeMethod(method, args)
Groovy 字符串
字符串可以使用双引号也可以使用单引号. 当使用双引号的时候,可以在其中包含内嵌值(embedded values).包含内嵌值的语法格式是${expression} 和Ruby语言中差不多,只是这里使用了$而Ruby中使用的是#.使用双引号的字符串其中包含了至少一个内嵌值,那么它就是groovy.lang.GString 类的对象. 其他的字符串都是java.lang.String类的对象. 在需要的时候Gstrings会被强制自动转换为java.lang.String.
针对Groovy类如groovy.lang.GString的Javadoc可以在http://groovy.codehaus.org/apidocs/找到.
内嵌值对于实现toString方法很有帮助. 例如,
String toString() { "${name} is ${age} years old." }
多行字符串可以用三种方式创建.下面的例子是等效的.最后一个例子使用了被称为 "here-doc"的方式. 在三个小于号之后,就是指定的分隔符字符串.字符串的值包括分隔符字符串出现的两次之间的所有字符. "EOS" (代表"End Of String"字符串的结尾的意思) 是个普通的分隔符,其他的分割符同样也可以使用.
s = " This string
spans three \"lines\"
and contains two newlines."
s = """ This string
spans three "lines"
and contains two newlines."""
s = <<<EOS
This string
spans three "lines"
and contains two newlines.
EOS
注意:在前面的代码片段中最后一行的字符没有被保存在字符串中.
以下的方法被添加到java.lang.String类中.
contains
该操作判断一个字符串中是否含有给定的子串. 'Groovy'.contains('oo') 则返回true.
count
该操作统计某子串在给定字符串中出现的次数. 'Groovy Tool'.count('oo')返回结果 2.
tokenize
该操作使用给定的分隔符把字符串分割成令牌(token)并返回令牌(token)的集合.指定分隔符参数是可选的.默认的间隔符是空格. 'apple^banana^grape'.tokenize('^')返回结果['apple', 'banana', 'grape'].
minus
该操作会除去字符串中给定子串第一次出现的部分. 'Groovy Tool' - 'oo' 返回'Grvy Tool'.
multiply
这个操作会把给定的字符串重复给定的次数. 'Groovy' * 3返回'GroovyGroovyGroovy'.
正则表达式 (regex)
首先,我们来回顾一下J2SE 1.4对正则表达式的支持.那么我们来看看Groovy是怎么实现的.
在J2SE 1.4中, java.util.regex包中的类支持正则表达式. 模式(Pattern) 对象代表一个编译过的regex.它们是使用Pattern.compile("pattern")创建的. 该类的Javadoc描述了正则表达式的语法. Matcher 对象保存一个模式与一个字符串匹配的结果.它们是使用pattern.matcher("text")创建的.为了判断文本是否与模式想匹配, 我们使用matcher.matches(). 模式中的/必须有与之相对应的/.
Groovy有3种方法可以支持正则表达式.
~"pattern" 创建一个模式对象并等同于 Pattern.compile("pattern").
"text" =~ "pattern" 创建一个 Matcher对象并等同于Pattern.compile("pattern").matcher("text").
"text" ==~ "pattern" – 返回一个布尔值,等同于Pattern.compile("pattern").matcher("text").matches().更多的方法可以参见模式和Matcher 的javadoc.
例如,
pattern = "\\d{5}" // matches zip codes (5 digits)
text = "63304" // a zip code
println text ==~ pattern // prints "true"
m = text =~ pattern
println m.matches() // prints "true"
// The next line requires a literal string for the pattern.
// A variable can't used.
p = ~"\\d{5}"
m = p.matcher(text)
println m.matches() // prints "true"
Groovy脚本
Groovy的脚本文件通常是以".groovy"为后缀名的. 它们可以包含 (以任何顺序)松散语句(loose statements), 与类无联系的方法定义, 以及类定义.
例如,
// These are loose statements.
println 'loose statement'
myMethod 'Mark', 19
println new MyClass(a1:'Running', a2:26.2)
// This is a method definition that
// is not associated with a class.
def myMethod(p1, p2) {
println "myMethod: p1=${p1}, p2=${p2}"
}
// This is a definition of a class that
// has two properties and one method.
class MyClass {
a1; a2
String toString() { "MyClass: a1=${a1}, a2=${a2}" }
}
方法和类的定义不需要放在它们被使用之前.在与类相关联的基本文件中松散方法会被编译为相应类中的静态方法.例如, Bar.groovy脚本中一个叫foo的松散方法会在类Bar中被编译成叫foo的静态方法.松散语句回被收集到run方法,由编译生成的main方法调用该方法
当groovyc被用来便宜一个脚本的时候,生成的类会有一个包含了所有的松散语句的run方法以及调用run方法的main方法.
目前脚本还不能引用其他脚本中的代码除非他们是编译好的并被引入了的. 这会在不久之后改进.
运算符重载
Groovy支持一部分运算符的运算符重载.每个运算符对应于一个特定的方法. 只要在你的类中实现这些方法就使用相应的运算符调用这些方法对这些类的对象进行操作.方法可以被重载来对不同类型的参数进行操作.
比较运算符
a == b对应a.equals(b)
a != b对应!a.equals(b)
a === b对应Java中的a == b
a <=> b对应a.compareTo(b)
a > b对应a.compareTo(b) > 0
a >= b对应a.compareTo(b) >= 0
a < b对应a.compareTo(b) < 0
a <= b对应a.compareTo(b) <= 0
比较运算符可以处理null值并且不会生成NullPointerException. Null被认为比任何值都小.
注意到在Groovy中== 运算符被用来判断两个对象是否具有相同的值而=== 运算符被用来判断它们是否是内存中的同一对象.
compareTo方法返回一个int 值,如果a < b则返回小于0的值, 如果a > b则返回大于0的值, 如果a 等于 b则返回0.
其他运算符
a + b对应a.plus(b)
a - b对应a.minus(b)
a * b对应a.multiply(b)
a / b对应a.divide(b)
a++ and ++a对应a.increment(b)
a-- and --a对应a.decrement(b)
a[b] 对应a.get(b)
a[b] = c对应a.put(b, c)
Groovy闭包
一个闭包就是可以使用参数的代码片段. 每个闭包会被编译成继承groovy.lang.Closure类的类.这个类有一个叫call方法,通过该方法我们可以传递参数并调用这个闭包.它们可以访问并修改在闭包创建的范围内的变量(They can access and modify variables that are in scope when the closure is created.)在闭包内创建的变量在闭包被调用的范围内同样可以被引用. 闭包可以保存在变量中并被作为参数传递到方法中.这在某些list, map和string方法非常有用,我们会在以后提到.
定义闭包的语法格式是
{ comma-separated-parameter-list | statements }
例如,
closure = { bill, tipPercentage | bill * tipPercentage / 100 }
tip = closure.call(25.19, 15)
tip = closure(25.19, 15) // 与上一行等效
传递了错误的参数数量会产生IncorrectClosureArgumentException.
关键字it用于只有一个参数的闭包.参数列表可以被省略而在语句中使用it代表参数.例如,下面的闭包是等效的.
{ x | println x }
{ println it }
下面是一个方法的例子,它使用了一个List和一个闭包作为参数.它被写成一个"松散方法",但是它同样可以被写成某个类的一个方法.方法遍历这个List, 对List中的每个项目调用闭包.它使用闭包返回true的项目生成一个新的List然后返回新的List. 注意Groovy提供的find和findAll方法可以替代它.
def List myFind(List list, Closure closure) {
List newList = []
for (team in list) {
if (closure.call team) newList.add team
}
newList
}
下面是使用这个方法的例子.
class Team { name; wins; losses }
teams = []
teams.add new Team(name:'Rams', wins:12 , losses:4)
teams.add new Team(name:'Raiders', wins:4 , losses:12)
teams.add new Team(name:'Packers', wins:10 , losses:6)
teams.add new Team(name:'49ers', wins:7 , losses:9)
winningTeams = myFind(teams) { it.wins > it.losses }
winningTeams.each { println it.name }
没必要编写一个象myFind的方法因为List类中 已经有一个findAll方法了.象下面这样使用它,
winningTeams = teams.findAll { it.wins > it.losses }
Groovy Beans
这里是一个 Groovy Bean例子.
class Car {
String make
String model
}
这个类声明了两个属性,而不包含任何方法.然而,很多事情是在后台完成的. 类,属性和方法默认是公共的(public).公共的和保护的(protected)属性会成为私有域但是它们的公共的/保护的get和 set 会被自动生成.(Public and protected properties result in private fields for which public/protected get and set methods are automatically generated)这些都可以被重载来提供定制的行为.对于明确被声明为私有的(private)属性来说, get和set 方法没有被生成.
上面的Groovy代码等同于以下的Java代码.
public class Car {
private String make;
private String model;
public String getMake() {
return make;
}
public String getModel() {
return model;
}
public void setMake(String make) {
this.make = make;
}
public void setModel(String model) {
this.model = model;
}
}
由Groovy Beans生成的类继承了java.lang.Object类并实现了groovy.lang.GroovyObject类.它添加的方法有getProperty, setProperty, getMetaClass, setMetaClass以及invokeMethod. groovy.lang.MetaClass类允许在运行时添加方法.
Groovy Beans可以使用有名参数创建.例如,下面的代码调用了Car类的无参数构造函数然后调用了每个独立属性的set方法.
myCar = new Car(make:'Toyota', model:'Camry')
Groovy Lists
Groovy lists是java.util.ArrayList类的实例.它们可以使用方括号内部的逗号分隔的值表来创建. 例如,
cars = [new Car(make:'Honda', model:'Odyssey'),
new Car(make:'Toyota', model:'Camry')]
println cars[1] //指的是Camry
for (car in cars) { println car } //调用Car的toString方法
class Car {
make; model
String toString() { "Car: make=${make}, model=${model}" }
}
为了确定从list的末端开始计算的list的元素的位置,我们使用负数索引(negative indexes).
空的lists可以用[]创建.例如,
cars = []
有两种方式添加元素.
cars.add car
cars << car
List可以使用数组调用array.toList()方法创建.数组同样可以使用list调用list.toArray()方法创建.
Groovy还向java.util.List添加了一些方法.
count
该操作计算list中有多少个元素与给定的对象相等.
[1, 2, 3, 1].count(1)返回2.
immutable
该操作使用java.util.Collections类的静态方法unmodifiableList创建一个集合的不能修改的拷贝.例如,
list = [1, 2, 3].immutable()
list.add 4 //抛出java.lang.UnsupportedOperationException异常
intersect
该操作创建一个含有两个给定list的公共元素的list.
[1, 2, 3, 4].intersect([2, 4, 6])返回[2, 4].
join
该操作用给定的字符串连接list中元素的toString的值.例如,它在list的所有字符串元素中间插入了一个’^’分隔符.
['one', 'two', 'three'].join('^')返回"one^two^three".
sort
该操作对list元素进行排序并创建一个新的list.排序可以接受用java.util.Comparator或闭包作为参数.
fruits = ['kiwi', 'strawberry', 'grape', 'banana']
// 下一行返回 [banana, grape, kiwi, strawberry].
sortedFruits = fruits.sort()
//下一行返回[kiwi, grape, banana, strawberry].
sortedFruits =
fruits.sort {l, r | return l.length() <=> r.length()}
上面的最后一个sort方法调用是把闭包作为方法参数的例子.在Groovy有很多方法可以做这件事.
Groovy Beans可以很容易的对多属性进行排序.假设有一个Player bean带有属性name, age和score.可以对这些bean的list players先基于age然后基于score进行排序.
players.sort { [it.age, it.score] }
min / max
这两个操作分别找出最小或最大的list元素或字符串字符.它们可以接受用java.util.Comparator或闭包作为参数. 例如, 它们找出一个list中最小和最大的数字.
[5, 9, 1, 6].min()返回1.
[5, 9, 1, 6].max()返回9.
reverse
该操作颠倒(反序)list中元素的位置或者字符串中字符的位置.
[1, 2, 3].reverse()返回[3, 2, 1].
Groovy重载了plus和minus运算符用于对java.util.List对象进行操作.
plus
该操作创建两个lists的一个合集,并且删去重复的元素.
[1, 2, 3] + [2, 3, 4]返回[1, 2, 3, 2, 3, 4].
minus
该操作删除第一个list中所有在第一个和第二个list都出现的元素.
[1, 2, 3, 4] - [2, 4, 6] 返回[1, 3].当list元素不是primitives,则使用 equals方法比较它们.
Groovy Maps
Groovy 的maps是java.util.HashMap类的实例. 他们可以使用方括号内部的逗号分隔的关键字/值对的list来创建. 关键字与值之间用冒号隔开. 例如,
players = ['baseball':'Albert Pujols',
'golf':'Tiger Woods']
println players['golf'] // 打印Tiger Woods
println players.golf // 打印Tiger Woods
for (player in players) {
println "${player.value} plays ${player.key}"
}
// 和前面的循环是一样的效果.
players.each {player |
println "${player.value} plays ${player.key}"
}
空的map可以使用 [:]创建.例如,
players = [:]
Groovy Switch
Groovy switch语句中可以使用任何对象包括类, List, Range和Pattern. Case语句使用isCase方法进行值的比较.这里提供了很多isCase方法的重载版本. 除非对于特定类型进行重载, 否则isCase使用equals方法.当一个case后面跟的是一个类名时, isCase使用instanceof. isCase方法在你的类中被重载.
下面是 switch语句对不同类型的值操作的例子.
switch (x) {
case 'Mark':
println "got my name"
break
case 3..7:
println 'got a number in the range 3 to 7 inclusive'
break
case ['Moe', 'Larry', 'Curly']:
println 'got a Stooge name'
break
case java.util.Date:
println 'got a Date object'
break
case ~"\\d{5}":
println 'got a zip code'
break
default:
println "got unexpected value ${x}"
}
Groovy Ranges
使用".."和"..."运算符创建Range.下面是一些例子.
3..7 创建一个range从3到7
3...7创建一个range从3到6
"A".."D"创建一个range从"A"到"D"
"A"..."D"创建一个range从"A"到"C"
Range是继承自java.util.AbstractList类并实现了groovy.lang.Range接口的类的对象.一个Range是一个不可修改的List. Range接口添加了getFrom和getTo方法来获得下界和上界的值. Range接口提供了两种实现. 当范围限制是整数值时使用groovy.lang.IntRange.它添加了一个contains 方法用于判断一个值是否在range内. 当范围限制是其他类型时使用groovy.lang.ObjectRange.它同样有contains方法,但是它仅仅只在作为范围限制的对象实现了java.lang.Comparable的时候有用.
Ranges 在循环中非常有用.参见下一部分的例子.
Groovy Looping
下面是在某一范围循环的6种方法.
for
for (i in 1..1000) { println i }
while
i = 1
while (i <= 1000) { println i; i++ }
each
(1..1000).each { println it }
times
1000.times { println it }
//循环从0到999的值
upto
1.upto(1000) { println it }
step
1.step(1001, 1) { println it }
//循环从1到1000的值;
// stopping one before the parameter value
List/Map/String方法接受闭包作为参数
一些List, Map和String方法接受闭包作为参数.
each
该操作用于遍历集合中的元素或者字符串中的字符.它为使用java.util.Iterator提供了另一种选择,可以得到更简单的代码.例如, 打印一个List中的每一个数
[5, 9, 1, 6].each {x | println x}
或
[5, 9, 1, 6].each {println it}
collect
该操作用于把一个集合或字符串转换成一个新的集合或字符串.例如,把List中的每个数都翻倍然后创建一个新的List
doubles = [5, 9, 1, 6].collect {x | x * 2}
把doubles赋值为[10, 18, 2, 12].
find
该操作用于找到第一个符合条件的集合元素或者字符串字符. 例如,找到list第一个大于5的数
[5, 9, 1, 6].find {x | x > 5}返回9.
findAll
该操作用于找到所有符合条件的集合元素或者字符串字符. 例如,找到list中所有大于5的数
[5, 9, 1, 6].findAll {x | x > 5} 返回 [9, 6].
every
该操作用于判断是否集合的每一个元素或者字符串的每一个字符都符合给定的条件. 例如, 判断List中所有的数是否都小于7
[5, 9, 1, 6].every {x | x < 7} 返回 false.
any
该操作用于判断是否存在集合中的元素或者字符串的字符符合给定的条件.例如,判断List中是否存在小于7的数
[5, 9, 1, 6].any {x | x < 7} 返回 true.
inject
该操作用于把值传给第一次遍历然后再把每次遍历的值作为参数传给下一次. 例如, 求5的阶乘(用一种不常见的方式)
factorial = [2, 3, 4, 5].inject(1) {
prevResult, x | prevResult * x
}
该闭包被执行了四次.
1) 1 * 2
2) 2 * 3
3) 6 * 4
4) 24 * 5
它返回120.
文件I/O
读取文件中的行(两种选择)
下面代码范例中的省略号 (...) 表示省略了的代码.
file = new File('myFile.txt')
file.eachLine { println it }
lineList = file.readLines()
读取文件中的字节(两种选择)
file = new File('myFile.txt')
file.eachByte { println it }
byteList = file.readBytes()
读取目录中的文件
dir = new File('directory-path')
dir.eachFile { file | . . . }
读取文件并关闭资源
这些方法通过一个Reader或者InputStream读取文件并且保证最后会关闭资源不管是否出现了例外.
file.withReader { reader | . . . }
reader.withReader { reader | . . . }
inputStream.withStream { is | . . . }
写文件并关闭资源
这些方法通过一个Writer或OutputStream写文件并且保证最后会关闭资源不管是否出现了例外.
file.withWriter { writer | . . . }
file.withPrintWriter { pw | . . . }
file.withOutputStream { os | . . . }
writer.withWriter { writer | . . . }
outputStream.withStream { os | . . . }
’<<’运算符
添加到字符串
s = 'foo'
s = s << 'bar'
添加到一个StringBuffer
sb = new StringBuffer('foo')
sb << 'bar'
添加到list
colors = ['red', 'green']
colors << 'blue'
写到数据流的末端
w = new File('myFile.txt').newWriter()
w << 'foo' << 'bar'
w.close()
对象导航(Object Navigation)
对象之间的关系(graphs)可以通过类似Xpath的语法使用点 (".")运算符来表现.为了避免产生NullPointerException例外, 使用运算符"->" 代替".".例如,
class Team {
String name
Person coach
players = []
}
class Person {
String name
}
p = new Person(name:'Mike Martz')
t = new Team(name:'Rams', coach:p)
//下一行打印和team.getCoach().getName()相同的内容.
println "coach = ${t.coach.name}"
t = new Team(name:'Blues')
// 下一行返回空,
//不会抛出NullPointerException例外.
println "coach = ${t->coach->name}"
// 下一行抛出NullPointerException例外.
println "coach = ${t.coach.name}"
Groovy Reflection
假设你想通过一个对象得到一个类对象.在Java中使用someObject.getClass()完成这件事.在Groovy中,使用someObject.class完成.
假设你想通过一个类名得到一个类对象.在Java和Groovy中使用SomeClass.class或Class.forName("pkg.SomeClass")完成这件事.
打印Groovy类 Gstring所有方法的列表,
GString.class.methods.each { it.name }
打印Java接口java.util.List中所有方法的列表,
java.util.List.class.methods.each { it.name }
捕捉没有实现的方法
可以编写类来捕捉没有实现的方法.例如,
o = new CatchCall()
// The next line prints "unknown method Mark called with [19]".
println o.foo("Mark", 19)
class CatchCall {
invokeMethod(String name, Object args) {
try {
return metaClass.invokeMethod(this, name, args)
} catch (MissingMethodException e) {
//可以在这里插入
//处理特定方法和参数的特殊逻辑.
return "unknown method ${name} called with ${args}"
}
}
}
Groovy Markup
Groovy的Markup 使用invokeMethod方法来捕捉刚刚提到的那些不存在的方法并把它们转换为”结点” ("nodes"). 方法的参数被看作是结点的属性.方法后的闭包被看作是结点的内容(Parameters to the methods are treated as attributes of the nodes.Closures after the methods are treated as the content of the nodes. )它有和多作用包括
· 构建泛型,数据结构树 (NodeBuilder)
· 构建DOM树(DOMBuilder)
· firing SAX事件(SAXBuilder)
· 创建HTML或XML的字符串 (MarkupBuilder)
· 执行Ant任务 (AntBuilder)
· 创建Swing用户接口(SwingBuilder)
另外, 定制的builder可以通过继承groovy.util.BuilderSupport类来创建.
生成HTML
下面是使用 MarkupBuilder生成HTML的例子.
import groovy.xml.MarkupBuilder
mb = new MarkupBuilder()
mb.html() {
head() {
title("This is my title.")
}
body() {
p("This is my paragraph.")
}
}
println mb
这段代码生成了下面的HTML文件.
<html>
<head>
<title>This is my title.</title>
</head>
<body>
<p>This is my paragraph.</p>
</body>
</html>
生成XML
下面是使用MarkupBuilder生成XML的例子.
import groovy.xml.MarkupBuilder;
mb = new MarkupBuilder()
mb.autos() {
auto(year:2001, color:'blue') {
make('Toyota')
model('Camry')
}
}
println mb
这段代码生成了下面的XML文件.
<autos>
<auto year='2001' color='blue'>
<make>Toyota</make>
<model>Camry</model>
</auto>
</autos>
Groovy SQL
Groovy使JDBC更简单. groovy.sql.Sql类提供了一个简单的方式来运行一个查询(query)以及遍历ResultSet中的的列.在下面的例子中, MusicCollection是一个数据库的名字(在本例中,已经被注册为一个ODBC 数据源), Artists是给数据库中一个表的名字,而Name则是该表中的列名.
import groovy.sql.Sql
dbURL = 'jdbc:odbc:MusicCollection'
jdbcDriver = 'sun.jdbc.odbc.JdbcOdbcDriver'
sql = Sql.newInstance(dbURL, jdbcDriver)
sql.eachRow('select * from Artists') {
println it.Name
}
Groovlets
Groovlets可以拿来取代Servlets或JSP.它提供了以下的隐性变量(implicit variables).
· out –等同于HttpServletResponse.getWriter()方法
· request – 等同于 HttpServletRequest
· session -等同于HttpSession
下面是一个Groovlet例子.它能保存为一个名为SimpleGroovlet.groovy的文件.它使用"here-doc"来生成HTML文件.
out.println <<<EOS
<html>
<head>
<title>My Simple Groovlet</title>
</head>
<body>
<h1>My Simple Groovlet</h1>
<p>Today is ${new java.util.Date()}.</p>
</body>
</html>
EOS
GroovyServlet编译Groovlet并会为它们提供缓存加速(cache)直到它们的内容发生改变.当Groovlet改变了之后,GroovyServlet会自动重新编译Groovlet. GroovyServlet必须在web.xml中注册.
下面是web.xml文件的一个例子仅仅示范了注册GroovyServlet的部分.
<?xml version="1.0"?>
<!DOCTYPE web-app
PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd">
<web-app>
<servlet>
<servlet-name>Groovy</servlet-name>
<servlet-class>groovy.servlet.GroovyServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>Groovy</servlet-name>
<url-pattern>*.groovy</url-pattern>
</servlet-mapping>
</web-app>
Groovlets可以使用Ant来部署.基本步骤是
1. 使用下面的内容创建一个WAR.
o 上面的Groovlet源文件(*.groovy)
o WEB-INF 文件夹中的web.xml
o WEB-INF/lib文件夹中的 groovy*.jar 和asm*.jar文件
2. 在一个servlet 引擎如Tomcat中部署这个WAR.
下面是做这件事的Ant构造文件.
build.properties
build.dir=build
src.dir=src
# Directory that contains Groovlets
groovy.dir=${src.dir}/groovy
# Directory that contains web.xml
web.dir=${src.dir}/web
# Path to WAR that will be produced
war.file=${build.dir}/${ant.project.name}.war
# Where the WAR should be deployed
webapps.dir=${env.TOMCAT_HOME}/webapps
# JARs that must be in the WAR
asm.jar=${env.GROOVY_HOME}/lib/asm-1.4.1.jar
groovy.jar=${env.GROOVY_HOME}/lib/groovy-1.0-beta-4-snapshot.jar
build.xml
<project name="GroovletExample" default="deploy">
<property environment="env"/>
<property file="build.properties"/>
<target name="prepare">
<mkdir dir="${build.dir}"/>
</target>
<target name="war" depends="prepare"
description="creates WAR file">
<war destfile="${war.file}" webxml="${web.dir}/web.xml">
<fileset dir="${groovy.dir}"/>
<lib file="${groovy.jar}"/>
<lib file="${asm.jar}"/>
</war>
</target>
<target name="deploy" depends="war"
description="deploys WAR file">
<delete dir="${webapps.dir}/${ant.project.name}"/>
<delete file="${webapps.dir}/${war.file}"/>
<copy file="${war.file}" todir="${webapps.dir}"/>
</target>
</project>
在这个Groovlet例子被部署了之后, 你可以让它在你浏览器中显示出来通过访问如下的地址http://localhost:8080/GroovletExample/SimpleGroovlet.groovy. GroovletExample是网络应用程序的名字. SimpleGroovlet.groovy是Groovlet的名字.这是与web.xml文件中的GroovyServlet指定的url-pattern相对应的.
Issues
Groovy还不算完美.浏览关于Groovy的文章,请访问http://groovy.codehaus.org/ 并点击Issue Tracker连接.下面是一些发表了的文章以及其编号.
· Primitive parameters to methods and closures aren't supported yet (128 & 133).
· Arrays of primitives aren't supported yet (119).
· Static primitive fields aren't supported yet (153).
· Chained assignment (x = y = 19) isn't supported yet (57).
· * imports aren't supported yet (84).
· Compiler doesn't catch calls to non-existent methods on statically typed parameters (170).
· Nested classes aren't supported yet (69).
总结
我们已经快速的浏览了Groovy的一些语法和特性.这些基于Java的捷径让你完成了更多的工作吗? 你在工作中找到了更多乐趣吗?你的代码是变得更容易理解还是更难了?我希望可以看到你们的反馈. 给我发Email :mark@ociweb.com. 同时你也可以在Groovy邮件列表中分享你的反馈在这里.
参考书目
Groovy的主页 - http://groovy.codehaus.org/