Groovy 学习手册(2)
二. 工具
1. 控制台
groovyConsole:
Groovy 控制台是一个非常易于使用和简单的轻量级的编辑器。你可以在里面做很多事情。
在编辑器里面可以书写代码,Windows 下,按下Ctrl + R
来运行代码,清除工作台的输出信息使用Ctrl + W
快捷键。
2. 编译
groovyc:
可以借助 Java 7 的动态调用设计的优势,可以使用--indy
标识符。这个在 Groovy 命令行下也同样适用。
动态调用可以帮助编译器提高性能,例如鸭子类型,元编程,方法缺失调用等。
3. Shell
groovysh:
在不需要交互的 shell 命令行下可以使用 groovysh 来执行 Groovy 代码。
4. 文档
groovydoc:
使用此命令可以从代码中生成文档。Groovy 里的代码的注释与 Java 是一样一样的。
/** This is a documentation comment. */
/* This is not */
// This is a one-line comment.
三. GDK
GDK(Groovy Development Kit ) 提供了很多适用和方便的方法,操作符,实用类还有其他的一些帮助类。
有些方法可以用在任何 Java 类中,例如each
, 还有更多。
1. 集合
Groovy 提供了很多方便快捷的方法来操作集合。
- sort —— 为集合排序
- findAll —— 根据匹配的闭包条件,查找所有的元素
- collect —— 一个遍历器,用来生成一个新的集合
- inject —— 遍历循环所有元素,但最后返回一个值
- each —— 根据给定的闭包条件遍历循环所有元素
- eachWithIndex —— 根据值和对应的索引来遍历循环所有的元素
- findIndexOf —— 根据核定的闭包条件返回第一个匹配的元素的索引
- any—— 如果匹配了闭包条件则返回 true
- every —— 如果匹配了闭包条件则返回 true
- first —— 获取 List 的第一个元素
- last —— 获取 List 的最后一个元素
- tail —— 返回 List 里面除了第一个元素意外剩下所有的元素
2.“*”操作符
“*”操作符可以用来访问集合里面所有元素的属性。在很多情况下,可以与collect
方法替换使用。
例如你想打印在变量名为dragons的集合里面每个对象的 name 的值,可以使用
dragons*.name.each { println it }
3. GPath
在 GPath 里面, GPath很多地方类似于XPath。List 和 Map 都支持属性标记,Groovy 提供了语法糖来非常容易地处理集合。请看下面的代码:
def listOfMaps = [['a': 11, 'b': 12], ['a': 21, 'b': 22]]
assert listOfMaps.a == [11, 21] //GPath notation
assert listOfMaps*.a == [11, 21] //spread dot notation
listOfMaps = [['a': 11, 'b': 12], ['a': 21, 'b': 22], null]
assert listOfMaps*.a == [11, 21, null] // caters for null values
assert listOfMaps*.a == listOfMaps.collect { it?.a } //equivalent notation
// But this will only collect non-null values
assert listOfMaps.a == [11,21]
4. IO
GDK 对 IO操作做了很多工作。
(1).Files
GDK 增加了很多方法用来非常便利地进行读写。
println path.toFile().text
在File 类中增加了 getText()
方法,非常简洁地读取整个文件。
new File("books.txt").text = "Modern Java"
使用 File 类中新增的setText()
方法,用来对文件进行写操作。关于二进制文件,可以使用bytes
属性。
byte[] data = new File('data').bytes
new File('out').bytes = data
如果你用InputStream 和 Reader 读取类,或者使用OutputStream 和Writer 写入类,还有如下方法:
new File('dragons.txt').withInputStream {in -> }
new File('dragons.txt').withReader {r -> }
new File('dragons.txt').withOutputStream {out ->}
new File('dragons.txt').withWriter {w -> }
最后,你可以使用eachLine
方法来读取文件的一行,例如:
new File('dragons.txt').eachLine { line->
println "$line"
}
//OR
new File('dragons.txt').eachLine { line, num ->
println "Line $num: $line"
}
在所有的情况中,即使出现异常,Grovvy 都能关闭 IO资源。
(2). URLs
GDK 提供了一个超级简单的方式来访问 URL。
下面的例子,是用 Java 代码来打开 Http 连接,从谷歌地址上读取数据并存放在 btye 数组中,然后打印所有的内容:
URL url = new URL("http://google.com");
InputStream input = (InputStream) url.getContent();
ByteArrayOutputStream out = new ByteArrayOutputStream();
int n = 0;
byte[] arr = new byte[1024];
while (-1 != (n = input.read(arr)))
out.write(arr, 0, n);
System.out.println(new String(out.toByteArray()));
然而,上面的代码用 Groovy 一句话就能搞定!!
println "http://google.com".toURL().text
(3.)Range
Range是 Groovy 内置类型。它可以被用在循环,switch 语句,抽取子字符串等。Range 的语法为start .. end
。
Range 用在each
方法和循环中非常便利:
(1..4).each {print it} //1234
for (i in 1..4) print i //1234
在 switch 语句中:
case 12..30: // Range 12 to 30
Tip
在 switch 语句中 Range 里的数据类型必须一致。
你还可以使用 Range 的 getAt
方法从字符串中截取子字符串:
def text = 'learning groovy'
println text[0..4] //learn
println text[0..4,8..-1] //learn groovy
Tip
-1表示集合或是字符串里最后一个元素。
你甚至可以在集合中使用 Range。
def list = ['hank', 'john', 'fred']
println list[0..1] //[hank, john]
你可以使用 “..<” 操作符定义 Range 的最后元素的开区间。
(1..<5).each {print it} //1234
(4.) 实用工具类
ConfigSlurper实用类用来读取配置信息,它是一种 Groovy 脚本。像 Java 中的*.properties的文件,可以使用“.”导航符进行访问,它也可以应用在闭包的配置的值和任何对象类型中。
def config = new ConfigSlurper().parse('''
app.date = new Date()
app.age = 42
app {
name = "Test${42}"
}
''')
def properties = config.toProperties()
assert properties."app.date" instanceof String
assert properties."app.age" == '42'
assert properties."app.name" == 'Test42'
Expando工具类用来创建动态扩展对象。你可以为对象添加属性和方法,在动态元编程中此类非常实用。例如:
def expando = new Expando()
expando.name = { -> 'Draco' }
expando.say = { String s -> "${expando.name} says: ${s}" }
expando.say('hello') // Draco says: hello
ObservableList/Map/Set
Groovy 提供了可供观察的 List,Map和 Set。当这些集合在新增,删除,修改元素时会触发PropertyChangeEvent( java.beans package)事件。需要注意的是,这个事件不仅会触发,它还持有属性的名字以及属性的新旧值。
例如,下面的例子使用ObservableList来打印所有触发的事件类。
def list = new ObservableList()
def printer = {e -> println e.class}
list.addPropertyChangeListener(printer)
list.add 'Harry Potter'
list.add 'Hermione Granger'
list.remove(0)
println list
// 打印结果为:
class groovy.util.ObservableList$ElementAddedEvent
class java.beans.PropertyChangeEvent
class groovy.util.ObservableList$ElementAddedEvent
class java.beans.PropertyChangeEvent
class groovy.util.ObservableList$ElementRemovedEvent
class java.beans.PropertyChangeEvent
[Hermione Granger]
四. 源于 Java
1. 静态方法默认值
Groovy 为方法参数提供了默认值,例如,下面的代码fly 方法有个名为 text 的参数
def fly(String text = "flying") {println text}
从 Java 的角度来看这需要创建两个重载的方法:
def fly() {println "flying"}
def fly(String text) {println text}
这个方法只要不与现存的方法冲突,可以设置任意长度的参数。
2. Equals, Hashcode , 更多方法
在 Java 中一项比较繁琐的操作就是提供 equal,hashcode 等方法。为了解决这个问题,Groovy 提供了 @EqualsAndHashCode 注解。只要放在类定义的上面,万事大吉了!
除此之外,我们通常根据属性来创建不同的构造方法,为此,Groovy 提供了 @TupleConstructor 注解。此注解非常容易第根据你属性的定义来定义构造方法。注意,此注解要放在定义类关键字 class 的前面。
当然,还有 @ToString 注解,用在放在 toString() 上。
当然,如果你想实现上面所有的功能,只需要一个 @Canonical 注解就全部搞定。
import groovy.transform.*
@Canonical class Dragon {def name}
println new Dragon("Smaug")
// prints: Dragon(Smaug)
assert new Dragon("").equals(new Dragon(""))
3. 正则表达式
在 Groovy 中使用了非常简单的方式来匹配正则表达式。在 Java 中你必须使用 java.util.regex.Pattern 类,在 Groovy 中只是一行就能搞定。
按照规范你必须使用斜杠来包围正则表达式。这样才允许你使用一些特殊的正则语法,从而免去了使用两个反斜线的痛苦写法。例如:
def isEmail = email ==∼ /[\w.]+@[\w.]+/
它相当于如下的 Java 代码:
Pattern patt = Pattern.compile("[\\w.]+@[\\w.]+");
boolean isEmail = patt.matches(email);
还可以只用操作符创建一个新的 Matcher类:
def email = 'mailto:adam@email.com'
def mr = email =∼ /[\w.]+@[\w.]+/
if (mr.find()) println mr.group()
这允许你在字符串内部使用正则表达式,并且从一个表达式中得到一个子组。
4. 缺失的Java语法
由于Groovy的独特的语法以及多年来Java一直增加新的东西,所以看起来Groovy缺失了一些功能。然而,我们可以通过其他方式来做这些相同的功能。例如,对于数组,对于 Groovy 来说是一个很困难的问题,因为Java创建数组的语法,数组里的值不会被编译,例如,下面的代码在Groovy里是报错的:
String[] array = new String[] {"foo", "bar"};
而应该使用这样的语法:
String[] array = ['foo', 'bar'].toArray()
还有,在很长时间内 for (Type item : list) 这种循环是不支持的。但是你有两种替代方案,一是使用 “in” 关键字;二 是使用 each 方法。
// for (def item : list)
for (item in list) doStuff(item)
list.each { item -> doStuff(item) }
5. 语句末尾“;”可选
如果你以前用过 Java 的话,在 Groovy 中因为分号是可选的,可能会引起语句结束的疑惑问题。通常这不是问题,但是在一行中调用多个方法的话,可能会产生问题。这种情况下,你需要在每行代码结束后使用非结束的操作符,例如“.”, 看例子:
class Pie {
def bake() { this }
def make() { this }
def eat() { this }
}
def pie = new Pie().
make().
bake().
eat()
如果你使用典型的Java语法的话,在Groovy里会引起编译错误:
def pie = new Pie() //Groovy interprets end of line
.make() // huh? what is this?
6. 泛型去哪里了?
Groovy 默认支持泛型,但并不是强制的。出于此原因,你可能在
Groovy 中很少会看到泛型的使用,例如,下面的代码在 Groovy 中是没有问题的。
List<Integer> nums = [1, 2, 3.1415, 'pie']
但是,如果想在 Groovy 中强制使用泛型的话,需要在类或方法前加上对应的注解 @CompileStatic 或 @TypeChecked。例如:
import groovy.transform.*
@CompileStatic
class Foo {
List<Integer> nums = [1, 2, 3.1415] //error
}
这样,就会引起编译错误:Incompatible generic argument types. Cannot assign java.util.List <java.lang.Number> to: java.util.List
7. Groovy 的数字
这里涉及的是十进制数字,在 Groovy 里默认为 BigDecimal 类型的。这样,你在做数学运算时省去了四舍五入的问题。
如果你想使用 float 和 double 类型,只需要在数字后面加上 f 或 d 就可以了。
def pie = 3.141592d
8. Boolean的解决方案
因为 Groovy 只是跟 Java 很相像,但并不是 Java,所有有时候会有些疑惑。其中一个就是 Boolean 的问题。
在 Boolean 的使用上,Groovy 更加自由。例如,下面的代码都可以表示 Boolean 类型为 true 的情况。
if ("foo") println("true")
if (!"") println("true")
if (42) println("true")
if (! 0) println("true")
9. Map的语法
Groovy 的语法糖可以直接在 Map 中使用字符串作为 key,这通常用起来非常方便。但是如果使用 Groovy 的属性访问语法糖时,但
Map中 元素的 key 是一个对象类型时,会导致迷惑不清。所以你需要直接使用 getClass() 方法。
除此而外,如果你使用变量作为 key 的话,也会出现类似的问题。这时你需要用括号把 key 括起来。例如:
1 def foo = 1
2 def bar = 2
3 def map = [(foo): bar]
如果变量 foo 不括起来的的话,就会变成了foo这个字符串,而不是数字1了。