介绍
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].
|
|||||
Groovy被设计为轻量级和容易嵌入到Java应用程序系统。 1、Groovy脚本样例 def run(foo) {
println 'Hello World!'
x = 123
foo * 10
}
run foo
l Groovy脚本样例包含run()函数,并在最后调用run(),返回结果 l 调用run()时传递的参数是foo属性(在运行脚本时,由外部提供属性值)
2、使用GroovyShell运行Groovy脚本 import groovy.lang.Binding;
import groovy.lang.GroovyShell;
import java.io.File;
public class EmbedGroovy {
private Binding binding = new Binding();
public Object getProperty(String name) {
return binding.getProperty(name);
}
public void setParameters(String[] paramNames, Object[] paramValues) {
int len = paramNames.length;
if (len != paramValues.length) {
System.out.println("parameters not match!");
}
for (int i = 0; i < len; i++) {
binding.setProperty(paramNames[i], paramValues[i]);
}
}
public Object runScript(String scriptName) {
GroovyShell shell = new GroovyShell(binding);
try {
return shell.evaluate(new File(scriptName));
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
public static void main(String[] args) {
EmbedGroovy embedGroovy = new EmbedGroovy();
String[] paramNames = { "foo" };
Object[] paramValues = { new Integer(2) };
embedGroovy.setParameters(paramNames, paramValues);
Object result = embedGroovy.runScript("src/Foo.groovy");
System.out.println(result);
System.out.println(embedGroovy.getProperty("foo"));
System.out.println(embedGroovy.getProperty("x"));
}
}
l GroovyShell的evaluate()方法运行指定文件的Groovy脚本,并返回运行结果(如果有的话) l 可以使用Binding的setProperty()方法在Groovy脚本运行之前设置需要的属性值,在创建GroovyShell对象时提供该Binding对象 l 同样,可以使用Binding的getProperty()方法在Groovy脚本运行之后获得指定的属性值,以便在后面的代码中使用 l 注意,在Groovy脚本中定义的变量(如x),都可以作为Binding中的属性被访问 l 由上面的例子可以看出,在Java中运行Groovy脚本,既可以输入,又可以有输出;因此,可以将Groovy脚本作为功能模块来实现具体功能,以提高编码效率
3、在Java中动态加载和运行Groovy代码 import groovy.lang.GroovyClassLoader;
import groovy.lang.GroovyObject;
import java.io.File;
public class DynamicGroovy {
private GroovyObject groovyObject;
public Object getProperty(String name) {
return groovyObject.getProperty(name);
}
public Object invokeScriptMethod(String scriptName, String methodName, Object[] args) {
ClassLoader parent = getClass().getClassLoader();
GroovyClassLoader loader = new GroovyClassLoader(parent);
try {
Class groovyClass = loader.parseClass(new File(scriptName));
groovyObject = (GroovyObject) groovyClass
.newInstance();
return groovyObject.invokeMethod(methodName, args);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
public static void main(String[] args) {
DynamicGroovy dynamicGroovy = new DynamicGroovy();
Object[] params = {new Integer(2)};
Object result = dynamicGroovy.invokeScriptMethod("src/Foo.groovy", "run", params);
System.out.println(result);
System.out.println(dynamicGroovy.getProperty("x"));
}
}
l 使用GroovyClassLoader可以在Java程序中动态加载Groovy类并执行它们(调用其方法) l GroovyClassLoader的parseClass()方法解析指定文件的Groovy脚本,生成对应的Groovy类并加载 l 可以创建GroovyObject接口的实例,调用其invokeMethod()方法来调用Groovy类的方法 l invokeMethod()方法的两个参数分别对应Groovy类的方法名和传递给该方法的参数列表数组 l 可以使用GroovyObject的getProperty()方法获得Groovy类的指定属性值 l 这里是直接传递参数值给Groovy类的方法,而不象前面一种方式,是通过Binding中的属性间接将参数值传递给Groovy类的方法 |