Groovy-一种快速开发的 JVM 语言
向您展示最佳实践和惯用解决方案,帮助您充分利用Groovy语言的力量来做解决方案。
1 关于 Groovy
Groovy是JavaTM虚拟机(JVM)的动态语言。它具有完整的对象导向、可脚本性、可选类型、运算符自定义、最常见数据类型的声明、闭包和范围等高级概念、紧凑的属性语法和无缝JavaTM集成。此参考卡提供了您在编程Groovy时可能查找的信息类型。
2 开始使用 Groovy
从http://groovy.codehaus.org安装Groovy,您将获得以下命令:
命令 | 说明 |
---|---|
groovy | 执行Groovy代码 |
groovyc | 编译Groovy代码 |
groovysh | 打开 Groovy shell |
groovyConsole | |
java2groovy | 迁移助手 |
Groovy命令附带-h和--help选项,以显示所有选项和所需的参数。典型的用法是:
执行文件MyScript.groovy
groovy MyScript
在命令行上计算(e)
groovy -e "print 12.5*Math.PI"
为每行输入打印(p)
echo 12.5 groovy -pe|"line.toDouble() * Math.PI"
内联编辑 (i) 文件 data.txt 通过反转每一行并保存备份
groovy -i.bak -pe"line.reverse()" data.txt
3 Groovy/Java集成
用Groovy,您可以像从Java一样调用任何Java代码。它是一样的。
用Java,您可以通过以下方式调用Groovy代码。请注意,您需要在class路径中放置groovy-all.jar。
交叉编译
使用groovyc、
评估
使用类groovy.util.Eval来评估Java字符串中捕获的简单代码:(int) Eval.xyz(1,2,3,"x+y+z");
GroovyShell
使用 groovy.util.GroovyShell 在 Binding 和可选的预解析中获得更大的灵活性:
GroovyShell shell= new GroovyShell();Script scpt = shell.parse("y = x*x");Binding binding = new Binding();scpt.setBinding(binding);binding.setVariable("x", 2);scpt.run();(int) binding.getVariable("y");
集成选项 | 特性/特性 |
---|---|
Eval/GroovyShell | 用于小表达式 + 重新加载,安全性 |
GroovyScriptEngine | 用于依赖脚本 + 重新加载 - 类、安全性 |
GroovyClassLoader | 包罗万象的解决方案 + 重新加载,安全性 |
Spring Beans | 与 Spring + reloading 集成 |
JSR-223 | 语言切换简单,但 API 受限 - 重新加载,安全性 需要 Java 6 |
4 语言元素
类和脚本
Groovy类声明看起来像Java。默认可见性修饰符是公开的
class MyClass { void myMethod(String argument) { }}
当 .groovy 文件或任何其他 Groovy 代码源包含未包含在类声明中的代码时,则此代码被视为脚本,例如
println "Hello World"
脚本与类的不同之处在于它们有一个 Binding 作为未声明引用的容器(在类中是不允许的)。
println text // expected in Bindingresult = 1 // is put into Binding
可选类型
静态类型可以像在 Java 中一样使用,并且在运行时会被遵守。通过用 def 关键字替换类型声明来使用动态类型。方法和闭包声明的形式参数甚至可以省略 def。
特性
无论使用什么类型,属性都被声明为具有默认可见性修饰符的字段。
class MyClass { String stringProp def dynamicProp}
Java 风格的 getter 和 setter 会自动编译到字节码中。
属性被称为
println obj.stringProp // getterobj.dynamicProp = 1 // setter
无论 obj 是用 Java 还是 Groovy 编写的,都会调用相应的 getter/setter。
Multimethods
方法由运行时类型调度,允许代码如
class Pers { String name boolean equals(Pers other) { name == other.name }}assert new Pers(name:'x') == new Pers(name:'x')assert new Pers(name:'x') != 1
5 运算符
可定制的运算符
可以通过实现/覆盖相应方法来自定义运算符。
运算符号 | 方法 | ||
---|---|---|---|
a + b | a.plus(b) | ||
a - b | a.minus(b) | ||
a * b | a.multiply(b) | ||
a / b | a.div(b) | ||
a % b | a.mod(b) | ||
a++ ++a | a.next() | ||
a-- --a | a.previous() | ||
a**b | a.power(b) | ||
a | b | a.or(b) | |
a&b | a.and(b) | ||
a^b | a.xor(b) | ||
~a | ~a a.bitwiseNegate() // sometimes referred to as negate | +a a.positive() // sometimes referred to as unaryMinus | -a a.negative() // sometimes referred to as unaryPlus |
a[b] | a.getAt(b) | ||
a[b] = c | a.putAt(b, c) | ||
a << b | a.leftShift(b) | ||
a >> b | a.rightShift(b) | ||
a >>> b | a.rightShiftUnsigned(b) | ||
switch(a){ case b: } [a].grep(b) if(a in b) | b.isCase(a) // b is a classifier | ||
a == b | a.equals(b) | ||
a != b | ! a.equals(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 | ||
a as B | a.asType(B) |
积极寻找机会在您自己的Groovy类中实现运算符方法。这通常会导致更具表现力的代码。典型的候选者是==、<=>、+、-、<<和isCase()。参见范围。
特殊运算符
运算符 | 含义 | NAME |
---|---|---|
a ? b : c | if (a) b else c | 三元,如果 |
a ?: b | a ? a : b | Elvis |
a?.b | a==null ? a : a.b | null safe |
a(*list) | a(list[0], list[1], ...) | spread |
list*.a() | [list[0].a(), list[1].a() ...] | spread-dot |
a.&b | 将对象a中方法b作为闭包的引用 | method closure |
a.@field | 直接访问属性 | dot-at |
6 简单的数据类型
数字类型
所有Groovy数字都是对象,而不是原始类型。字面声明是:
TYPE | EXAMPLE LITERALS |
---|---|
java.lang.Integer | 15, 0x1234ffff |
java.lang.Long | 100L, 100l |
java.lang.Float | 1.23f, 4.56F |
java.lang.Double | 1.23d, 4.56D |
java.math.BigInteger | 123g, 456G |
java.math.BigDecimal | 1.23, 4.56, 1.4E4, 2.8e4, 1.23g, 1.23G |
数学运算的强制规则的一些例子是:
表达式 | 结果类型 |
---|---|
1f * 2f | Double |
1f / 2f | Double |
(Byte)1 + (Byte)2 | Integer |
1 * 2L | Long |
1 / 2 | BigDecimal (0.5) |
(int)(1/2) | Integer (0) |
1.intdiv(2) | Integer (0) |
Integer.MAX_VALUE+1 | Integer |
2**31 | Integer |
2**33 | Long |
2**3.5 | Double |
2G + 1G | BigInteger |
2.5G + 1G | BigDecimal |
1.5G == 1.5F | Boolean (true) |
1.1G == 1.1F | 1.1G == 1.1F |
字符串类型
'literal String''''literalmultiline String'''def lang = 'Groovy'"GString for $lang""$lang has ${lang.size()} chars""""multiline GString withlate eval at ${-> new Date()}"""
GStrings 中的占位符在声明时被取消引用,但它们的文本表示在 GString -pString 转换时被查询。
/String with unescaped \ included/
正则表达式
正则表达式查找运算符 =~
正则表达式匹配运算符 ==~
正则表达式模式运算符 ~String
Examples:
def twister = 'she sells sea shells'
// 包含关键字 'she'assert twister =~ 'she'// 以 'she' 开头 且 以 'shells' 结尾assert twister ==~ /she.*shells/// 预编译def pattern = ~/she.*shells/assert pattern.matcher(twister).matches()// 匹配是可迭代的 以 'sh' 开头的单词def shwords = (twister =~ /\bsh\w*/).collect{it}.join(' ')assert shwords == 'she shells'// 通过逻辑替换assert twister.replaceAll(/\w+/){ it.size()} == '3 5 3 6'// 正则表达式组到闭包参数 查找开头和结尾相同的单词def matcher = (twister =~ /(\w)(\w+)\1/)matcher.each { full, first, rest -> assert full in ['sells','shells'] assert first == 's'}
符号 | 含义 | |
---|---|---|
. | 任何字符 | |
^ | 行首(或文档开始,在单行模式下) | |
$ | 行尾(或文档结尾,在单行模式下) | |
\d | 数字字符 | |
\D | 除数字外的任何字符 | |
\s | 空白字符 | |
\S | 除空格外的任何字符 | |
\w | 单词字符 | |
\W | 除单词字符外的任何字符 | |
\b | 词界 | |
() | 分组 | |
(x | y) | x 或 y 如 (Groovy |
\1 | 反向匹配到第一组,例如用 (.)\1 查找双倍字符 | |
x* | x 出现零次或多次。 | |
x+ | x 出现一次或多次。 | |
x? | x 出现 0 次或 1 次。 | |
x{m,n} | x 至少出现“m”次,最多出现“n”次。 | |
x{m} | x 恰好出现“m”次。 | |
[a-f] | 包含字符'a'、'b'、'c'、'd'、'e'、'f'的字符类 | |
[^a] | 包含除“a”之外的任何字符的字符类 | |
(?is:x) | 评估 x 时切换模式;i 打开 ignoreCase, s 单行模式 | |
(?=regex) | positive lookahead | |
(?<=text) | positive lookbehind |
7 集合类型
范围
范围包括0..10或半完全像0.。<10。它们通常被括在括号中,因为范围操作符的优先级较低。
assert (0..10).contains(5)assert (0.0..10.0).containsWithinBounds(3.5)for (item in 0..10) { println item }for (item in 10..0) { println item }(0..<10).each { println it }
整数范围通常用于选择子列表。范围边界可以是定义pret()、next()并实现Comparable的任何类型。值得注意的例子是字符串和日期。
列表
列表看起来像数组,但类型为java.util.List以及新方法。
[1,2,3,4] == (1..4)[1,2,3] + [1] == [1,2,3,1][1,2,3] << 1 == [1,2,3,1][1,2,3,1] - [1] == [2,3][1,2,3] * 2 == [1,2,3,1,2,3][1,[2,3]].flatten() == [1,2,3][1,2,3].reverse() == [3,2,1][1,2,3].disjoint([4,5,6]) == true[1,2,3].intersect([4,3,1]) == [3,1][1,2,3].collect{ it+3 } == [4,5,6][1,2,3,1].unique().size() == [1,2,3,1].count(1) == [1,2,3,4].min() == [1,2,3,4].max() == [1,2,3,4].sum() == [4,2,1,3].sort() == [1,2,3,4][4,2,1,3].findAll{it%2 == 0} == [4,2]def anims=['cat','kangaroo','koala']anims[2] == 'koala'def kanims = anims[1..2]anims.findAll{it =~ /k.*/} ==kanimsanims.find{ it =~ /k.*/} ==kanims[0]anims.grep(~/k.*/) ==kanims
经常使用sort()方法,有三种方式:
Sort 调用 | 使用 |
---|---|
col.sort() | 可比对象的自然排序 |
col.sort { it.propname } | 在比较结果之前,对每个项目应用闭包 |
col.sort { a,b -> a <=> b } | 闭包为每次比较定义了一个比较器 |
列表也可以用负索引和反向范围进行索引。
def list = [0,1,2]assert list[-1] == 2assert list[-1..0] == list.reverse()assert list == [list.head()] + list.tail()
子列表分配可以使列表增长或缩小,列表可以包含不同的数据类型。
list[1..2] = ['x','y','z']assert list == [0,'x','y','z']
Maps
Maps就像具有任意类型键而不是整数的列表。因此,语法非常对齐。
def map = [a:0, b:1]
Maps可以通过传统的方括号语法访问,或者好像key是Maps的属性。
assert map['a'] == 0assert map.b == 1map['a'] = 'x'map.b = 'y'assert map == [a:'x', b:'y']
还有一个显式的 get 方法可以选择采用默认值。
assert map.c == nullassert map.get('c',2) == 2assert map.c == 2
Map迭代方法考虑了Map.Entry对象的性质。
map.each { entry -> println entry.key println entry.value}map.each { key, value -> println "$key $value"}for (entry in map) { println "$entry.key $entry.value"}
GPath
调用列表中的属性返回列表中每个项目的属性列表。
employees.address.town
返回town对象列表。
要对方法调用执行同样操作,请使用扩展点运算符。
employees*.bonus(2008)
calls the bonus method on each employee and stores theresult in a list.
闭包
闭包捕获一段逻辑和封闭范围。它们是一流的对象,可以接收消息,可以从方法调用返回,存储在字段中,并用作方法调用的参数。
在方法参数中使用
def forEach(int i, Closure yield){ for (x in 1..i) yield(x)}
用作最后一个方法参数
forEach(3) { num -> println num }
构造并分配给局部变量
def squareIt = { println it * it}forEach(3, squareIt)
将最左边的闭包参数绑定到固定参数
def multIt = {x, y -> println x * y}forEach 3, multIt.curry(2)forEach 3, multIt.curry('-')
闭包参数列表示例:
Closure.isCase(b)将b发送到闭包,并将调用结果返回为布尔值。例如
switch ('xy'){case {it.startsWith('x')} :...}[0,1,2].grep { it%2 == 0 }
8 GDK
java.lang.Object的方法
获取对象信息
println obj.dump()
或在 GUI 中
import groovy.inspect.swingui.*ObjectBrowser.inspect(obj)
打印obj的属性、方法和字段
println obj.propertiesprintln obj.class.methods.nameprintln obj.class.fields.name
动态调用方法的两种方式
obj.invokeMethod(name, paramsAry)obj."$name"(params)
其他方法
is(other) // identity checkisCase(candidate) //default:equalityobj.identity {...}; obj.with {...}print(); print(value),println(); println(value)printf(formatStr, value)printf(formatStr, value[])sleep(millis)sleep(millis) { onInterrupt }use(categoryClass) { ... }use(categoryClassList) { ... }
每个对象在 Groovy 中都是可迭代的,即使它是用 Java 实现的。您不仅可以在循环中使用任何 obj,例如
for (element in obj) { ... }
但您也可以应用以下迭代对象方法:
返回类型 | PURPOSE |
---|---|
Boolean | any {...} |
List | collect {...} |
Collection | collect(Collection collection) {...} |
(void) | each {...} |
(void) | eachWithIndex {item, index-> ...} |
Boolean | every {...} |
Object | find {...} |
List | findAll {...} |
Integer | findIndexOf {...} |
Integer | findIndexOf(startIndex) {...} |
Integer | findLastIndexOf {...} |
Integer | findLastIndexOf(startIndex) {...} |
List | findIndexValues {...} |
List | findIndexValues(startIndex) {...} |
Object | inject(startValue) {temp, item -> ...} |
List | grep(Object classifier) // uses classifier.isCase(item) |
实现返回 Iterator 对象的 iterator() 方法,以使用上述方法为您自己的 Groovy 类提供有意义的可迭代行为。
文件和 I/0
常用的文件系统方法
def dir = new File('somedir')def cl = {File f -> println f}dir.eachDir cldir.eachFile cldir.eachDirRecurse cldir.eachFileRecurse cldir.eachDirMatch(~/.*/, cl)dir.eachFileMatch(~/.*/, cl)
常用的读方法
def file = new File('/data.txt')println file.text(also for Reader, URL, InputStream,Process)def listOfLines = file.readLines()file.eachLine { line -> ... }file.splitEachLine(/\s/) { list -> }file.withReader { reader -> ... }(also for Reader, URL, InputStream)file.withInputStream { is -> ...}(also for URL)
常用的写方法
out << 'content'for out of type File, Writer, OutputStream, Socket, and Processfile.withWriter('ASCII') {writer -> }file.withWriterAppend('ISO8859-1'){writer -> ... }
使用字符串读写
def out = new StringWriter()out << 'something'def str = out.toString()def rdr = new StringReader(str)println rdr.readLines()
连接读写
writer << reader
可写对象的特殊逻辑,例如 writeTo()
writer << obj
变换(闭包返回替换)和过滤器(闭包返回布尔值)
reader.transformChar(writer){c -> }reader.transformLine(writer){line-> }src.filterLine(writer){line-> }writer << src.filterLine {line -> }For src in File, Reader, InputStream
线程与进程
产生新线程的两种方式
def thread = Thread.start { ... }def t = Thread.startDaemon { ... }
与外部进程通信的两种方式('cmd /c' 仅适用于 Windows 平台)
today = 'cmd /c date /t'.execute().text.split(/\D/)proc = ['cmd','/c','date'].execute()Thread.start {System.out << proc.in}Thread.start {System.err << proc.err}proc << 'no-such-date' + "\n"proc << today.join('-') + "\n"proc.out.close()proc.waitForOrKill(0)
9 XML
读 XML
决定使用解析器(用于基于状态的处理)或 slurper(用于基于流的处理)
def parser = new XmlParser()def slurper = new XmlSlurper()
常见的解析方法: |
---|
parse(org.xml.saxInputSource) |
parse(File) |
parse(InputStream) |
parse(Reader) |
parse(String uri) |
parseText(String text) |
parser 和 slurper 的 parse 方法返回不同的对象(Node vs. GPathResult),但您可以在两者上应用以下方法:
result.name()result.text()result.toString()result.parent()result.children()result.attributes()result.depthFirst()result.iterator() // see GDK hot tip
子项、子项和属性访问的简写:
简写 | RESULT |
---|---|
['elementName'] | All child elements of that name |
.elementName | All child elements of that name |
[index] | Child element by index |
['@attributeName'] | The attribute value stored under that name |
.'@attributeName' | The attribute value stored under that name |
.@attributeName | The attribute value stored under that name |
从博客中读取前十个标题:
def url= 'http://'+'www.groovyblogs.org/feed/rss'def rss = new XmlParser().parse(url)rss.channel.item.title[0..9]*.text()
写 XML
Groovy (Streaming-) MarkupBuilder 允许您使用逻辑生成适当的 XML,同时保持声明式样式。
def b=new groovy.xml.MarkupBuilder()b.outermost { simple() 'with-attr' a:1, b:'x', 'content' 10.times { count -> nesting { nested count } }}
10 SQL
连接到DB的
直接获取一个新的 Sql 实例。例如,一个 HSQLDB
import groovy.sql.Sqldef db = Sql.newInstance('jdbc:hsqldb:mem:GInA','user-name','password','org.hsqldb.jdbcDriver')
使用数据源的替代方法
import org.hsqldb.jdbc.*def source = new jdbcDataSource()source.database = 'jdbc:hsqldb:mem:GInA'source.user = 'user-name'source.password = 'password'def db = new groovy.sql.Sql(source)
提交查询
当查询包含通配符时,使用PreparedStatement是明智的。当您在额外列表中提供值列表或语句为GString时,Groovy SQL会自动执行此操作。因此,以下每种方法都有三种变体:
method('SELECT ... ')method('SELECT ...?,?', [x,y])method("SELECT ... $x,$y")
RETURNS | METHOD NAME | PARAMETERS |
---|---|---|
boolean | execute | prepStmt |
Integer | executeUpdate | prepStmt |
void | eachRow | prepStmt { row -> } |
void | query | prepStmt { resultSet -> ... } |
List | rows | prepStmt |
Object | firstRow | prepStmt |
在上面,可以通过索引或名称从每一行中获取属性
db.eachRow('SELECT a,b ...'){ row -> println row[0] + ' ' + row.b}
与 GPath 结合
List hits = db.rows('SELECT ...')hits.grep{it.a > 0}
DataSet
无需SQL即可轻松进行数据库操作
def dataSet = db.dataSet(tablename)dataSet.add ( a: 1, b: 'something')dataSet.each { println it.a }dataSet.findAll { it.a < 2 }
在最后一条语句中,findAll 闭包中的表达式将直接映射到 SQL WHERE 子句。
11 元编程
Categories
在运行时分配给实现共同目的的任意类的一组方法。适用于一个线程。范围仅限于闭包。
class IntCodec { static String encode(Integer self){self.toString()} static Integer decode(String self){self.toInteger()}}use(IntCodec) {42.encode().decode()}
ExpandoMetaClass
相同的示例,但更改适用于所有线程和无限范围。
Integer.metaClass.encode << {delegate.toString()}String.metaClass.decode << {delegate.toInteger()}42.encode().decode()
方法调用钩子
在您的 Groovy 类中,实现该方法
Object invokeMethod(String name, Object args)
拦截对不可用方法的调用。
此外,实现接口 GroovyInterceptable 以拦截对可用方法的调用。
Implement
Object getProperty(String name)void setProperty(String name, Object value)
拦截属性访问。
变体更容易处理
Object methodMissing(String name, Object args)Object propertyMissing(String name, Object args)
就像名字所暗示的那样。
除了实现上述方法外,还可以将它们添加到任意类(或对象)的 MetaClass 中,以达到相同的效果。
Integer.metaClass.methodMissing << { String name, Object args -> Math."$name"(delegate)}println 3.sin()println 3.cos()
CLOSURE | PARAMETERS |
---|---|
{ ... } | zero or one (implicit 'it') |
{-> ... } | zero |
{x -> ... } | one |
{x=1 -> ... } | one or zero with default |
{x,y -> ... } | two |
{ String x -> ... } | one with static type |