官方文档
http://www.groovy-lang.org/documentation.html
http://docs.groovy-lang.org/latest/html/documentation/core-metaprogramming.html#_groovyobject_interface
IO: https://www.jianshu.com/p/5732fc8b49e5
Collection: https://www.jianshu.com/p/67f78abd7242
Runtime and compile-time metaprogramming: https://www.jianshu.com/p/b0ebb5c5e562
Testing guide: https://www.jianshu.com/p/8c3afa45c7c7
Integrating Groovy into applications: https://www.jianshu.com/p/40106170745e
Design patterns in Groovy: https://www.jianshu.com/p/005c412c8ae3
Parsing and producing JSON: https://www.jianshu.com/p/3c28985dc872
一、环境搭建
Linux下就是下载一个tar包,解压,使用 bin/
目录中的 groovy
、groovyc
、groovysh
命令
或者安装后可以直接使用这些命令
Windows下推荐使用eclipse插件
参考官方文档 https://github.com/groovy/groovy-eclipse/wiki
或者直接下载插件包离线安装
二、语法
1、注释
1.1、单行注释和多行注释
和Java类似
// a standalone single line comment
println "hello" // a comment till the end of the line
/* a standalone multiline comment
spanning two lines */
println "hello" /* a multiline comment starting
at the end of a statement */
println 1 /* one */ + 2 /* two */
1.2、GroovyDoc 注释
和java类似
/**
* A Class description
*/
class Person {
/** the name of the person */
String name
/**
* Creates a greeting method for a certain person.
*
* @param otherPerson the person to greet
* @return a greeting message
*/
String greet(String otherPerson) {
"Hello ${otherPerson}"
}
}
1.3、Shebang line
#!/usr/bin/env groovy
println "Hello from the shebang line"
2、Identifiers
不能以数字开头
不能是表达式
2.1、Quoted identifiers
引号引起来的标识符
它出现于 .
表达式的后面。
例如,person.name
可以使用 person."name"
表达式表示
当属性包含空格和其他不合法字符串时,这非常有趣
例如
def map = [:]
map."an identifier with a space and double quotes" = "ALLOWED"
map.'with-dash-signs-and-single-quotes' = "ALLOWED"
assert map."an identifier with a space and double quotes" == "ALLOWED"
assert map.'with-dash-signs-and-single-quotes' == "ALLOWED"
这种用法还可以用于动态指定key,例如
map.'single quote'
map."double quote"
map.'''triple single quote'''
map."""triple double quote"""
map./slashy string/
map.$/dollar slashy string/$
def firstname = "Homer"
map."Simpson-${firstname}" = "Homer Simpson"
assert map.'Simpson-Homer' == "Homer Simpson"
3、字符串
Groovy允许你实例化 java.lang.String
对象,也可以是 GString
( groovy.lang.GString
), 即解释型的字符串。
// 单引号引起来的字符串不支持解释
'a single quoted string'
'''line one
line two'''
// 字符串相加
assert 'ab' == 'a' + 'b'
// 如果你的代码是缩进的,你可以使用GDK的 String#stripIndent() 和 String#stripMargin() 方法,
// 它接受一个分隔符来标识要从字符串开头处移除的文本
//注意如下的方式创建字符串会在字符串开头包含一个\n
def startingAndEndingWithANewline = '''
line one
line two
line three
'''
// 你可以使用转义来消除
def strippedFirstNewline = '''\
line one
line two
line three
'''
assert !strippedFirstNewline.startsWith('\n')
3.1、 转义特殊字符
使用 \
3.2、双引号引起来的字符串
如果没有解释表达式,就是 java.lang.String
字符串
如果有解释表达式,就是 groovy.lang.GString
3.2.1、解释字符串
使用 ${}
包含表达式,或者简单的使用 $
${}
可以是任意的表达式组合,并且取最后一个表达式返回的值嵌入到字符串中
例如
println "The sum of 1 and 2 is equal to ${def a = 1; def b = 2; a + b}"
也可以只使用 $
而不提供 {}
,但是如果这样使用,则表达式仅限于 a.b
、a.b.c
这样的格式
但是包含方法调用括号、闭包花括号或算术运算符的表达式将是无效的
例如
def number = 3.14
//下面的语句将抛出异常, 这是因为Groovy认为你正在尝试访问number的toString属性,而不是调用number的toString()方法
println "$number.toString()"
//你可以使用
println "${number.toString()}"
如果你需要转义 $
或${}
,只需要转义 $
即可,即 \$
3.2.2、延迟解释
${->}
表示延迟解释,即表示这声明了一个真正的 closure
例如
def number = 1
def eagerGString = "value == ${number}"
def lazyGString = "value == ${ -> number }"
assert eagerGString == "value == 1"
assert lazyGString == "value == 1"
number = 2
assert eagerGString == "value == 1"
assert lazyGString == "value == 2"
${}
字符串也并非是一成不变,只是它仅仅表示指向的对象不变
如下所示
class Person {
String name
String toString() {
name
}
}
def sam = new Person(name:'Sam')
def gs = "Name: ${sam}"
assert gs == 'Name: Sam'
sam.name = 'Lucy'
assert gs == 'Name: Lucy'
// 看到了吗, gs仅仅表示指向的对象 sam 不变,但是sam.name 属性变了,gs表示的字符串也会变化
当一个方法(无论是使用Java实现还是Groovy实现)期望接收一个 java.lang.String
对象,但是我们传入了一个 groovy.lang.GString
对象进去,它会自动调用 groovy.lang.GString
的 toString()
方法将其转换为 java.lang.String
对象
3.2.3 GString 和 String 的 hashCodes
虽然解释型字符串可以用来代替普通的Java字符串,但是它们的hashCode不同!!
普通Java字符串是不可变的,而 GString
的结果字符串表示可以根据其内插值而变化。
即使对于相同的结果字符串,GStrings
和 Strings
也 不 具有相同的hashCode。
例如:
// 它们的hashCode不同
assert "one: ${1}".hashCode() != "one: 1".hashCode()
def key = "a"
def m = ["${key}": "letter ${key}"]
println m
// 输出 [a:letter a]
assert m["a"] == null
m["${key}"]="new A"
println m
// 输出 [a:letter a, a:new A]
assert m.get("a") == "new A"
assert m.get("${key}") == "letter a"
// 这是为什么呢?
// 因为 map 初始化时使用的是 map.put(Object key,Object value)方法,所以初始化时使用 GString ,key 是一个GString,而不是 String
// 而使用下标索引时 m["a"]=m["${key}"]=m.getAt(String property) 使用的是 map.getAt(String prop)方法,索引的key是指定的字符串属性,
// 所以你使用 m["a"]或m.getAt("a") 无法索引到GString 类型的key,你要索引它应该使用 m.get("${key}")
// 同样,使用下标索引设置值时 m["${key}"]="new A" 等价于 m.putAt(String prop,Object value) ,
// 该方法接收一个字符串对象,所以GString会自动转换为 String,
// 而使用 m.put(Object key,Object value) 时,GString则不会转换为 String
3.2.4 Slashy字符串
使用 //
包裹的字符串
用于定义正则表达式
例如
def fooPattern = /.*foo.*/
assert fooPattern == '.*foo.*'
def escapeSlash = /The character \/ is a forward slash/
assert escapeSlash == 'The character / is a forward slash'
注意空的slashy字符串不能使用//表示
因为 //
是注释
例如
// 下面的语句会编译错误
assert '' == //
3.2.5 Dollar slashy字符串
Dollar slashy字符串是指 $/
开头,/$
结尾的字符串
转义符是 $
, 并且它可以转义另一个 $
, 或 /
, 但是这不是必须的,除非必须转义。
例如
def name = "Guillaume"
def date = "April, 1st"
def dollarSlashy = $/
Hello $name,
today we're ${date}.
$ dollar sign
$$ escaped dollar sign
\ backslash
/ forward slash
$/ escaped forward slash
$$$/ escaped opening dollar slashy
$/$$ escaped closing dollar slashy
/$
assert [
'Guillaume',
'April, 1st',
'$ dollar sign',
'$ escaped dollar sign',
'\\ backslash',
'/ forward slash',
'/ escaped forward slash',
'$/ escaped opening dollar slashy',
'/$ escaped closing dollar slashy'
].every { dollarSlashy.contains(it) }
3.2.6 Characters
不像Java,Groovy没有显式的字符声明格式,但是你可以显式将一个字符串声明为字符类型
例如
// 声明时指定类型
char c1 = 'A'
assert c1 instanceof Character
// 使用 as 声明
def c2 = 'B' as char
assert c2 instanceof Character
// 使用强转声明
def c3 = (char)'C'
assert c3 instanceof Character
4、数字
4.1、整数类型
和Java一样
byte char short int long
java.lang.BigInteger
如果你不是显式的声明数字的类型(使用 def
声明数字),那么数字的类型取决于你声明的数字的大小,依次为 Integer、Long、BigInteger
以 0b
开头声明数字表示使用二进制
以 0
开头声明数字表示使用八进制
以 0x
开头声明数字表示使用十六进制
4.2 Decimal literals
十进制数字,和Java一样
float double
java.lang.BigDecimal
可以使用 e
或 E
, 例如
assert 1e3 == 1_000.0
assert 2E4 == 20_000.0
assert 3e+1 == 30.0
assert 4E-2 == 0.04
assert 5e-1 == 0.5
使用 def
声明十进制数字,默认使用的类型是 java.lang.BigDecimal
不能使用 二进制、八进制、十六进制表示
4.3 数字类型后缀
使用 G/g
表示 BigInteger
使用 L/l
表示 Long
使用 I/i
表示 Integer
使用 G/g
表示 BigDecimal
使用 D/d
表示 Double
使用 F/f
表示 Float
除法/
会产生一个 double
或 BigDecimal
结果
任何一个参数是 float
或 double
时,结果是 double
其他情况时返回 BigDecimal
对象
要想执行Java中的整数除法,使用 intdiv()
方法
幂操作符**
产生的结果:
基本上应该是 如果结果可以表示为 Integer/Long
,那就返回一个 Integer/Long
对象,
否则返回 Double
对象或 BigDecimal
对象
5、Lists
Groovy使用 [,,,]
格式表示 lists
Groovy list是普通的 java.util.List
,它没有定义自己的集合类
定义列表文字时使用的具体列表实现默认为java.util.ArrayList
,除非您决定另外指定
例如
def numbers = [1, 2, 3]
assert numbers instanceof List
assert numbers.size() == 3
def heterogeneous = [1, "a", true]
def arrayList = [1, 2, 3]
assert arrayList instanceof java.util.ArrayList
def linkedList = [2, 3, 4] as LinkedList
assert linkedList instanceof java.util.LinkedList
LinkedList otherLinked = [3, 4, 5]
assert otherLinked instanceof java.util.LinkedList
你可以使用 []
下标来访问和设置列表元素
使用 负数 的下标,可以从列表结尾向前索引
使用 <<
操作符追加元素到列表
使用 [a,b,c,...]
或 [a..b]
同时访问数组的多个元素,并返回一个list, 其中 [a..b]
用法中 a可以比b大,并且会自动反向索引元素
6、Arrays
和 List类似,不过你需要显式地将其声明为数组类型
String[] arrStr = ['Ananas', 'Banana', 'Kiwi']
assert arrStr instanceof String[]
assert !(arrStr instanceof List)
def numArr = [1, 2, 3] as int[]
assert numArr instanceof int[]
assert numArr.size() == 3
你还可以创建多维数组
def matrix3 = new Integer[3][3]
assert matrix3.size() == 3
Integer[][] matrix2
matrix2 = [[1, 2], [3, 4]]
assert matrix2 instanceof Integer[][]
7、Maps
上代码
def colors = [red: '#FF0000', green: '#00FF00', blue: '#0000FF']
assert colors['red'] == '#FF0000'
assert colors.green == '#00FF00'
colors['pink'] = '#FF00FF'
colors.yellow = '#FFFF00'
assert colors.pink == '#FF00FF'
assert colors['yellow'] == '#FFFF00'
assert colors instanceof java.util.LinkedHashMap
Groovy使用 java.util.LinkedHashMap
创建 maps
访问不存在的key会返回 null
除了可以使用字符串作为key,还可以使用其他类型的对象作为key
有时候你可能想使用变量声明map,但是你不能使用如下方式:
def key = 'name'
def person = [key: 'Guillaume']
// 不包含 name key
assert !person.containsKey('name')
// 而是包含了字面值 key 作为key
assert person.containsKey('key')
你应该使用下面的方式
def key = 'name'
person = [(key): 'Guillaume']
// 注意此时不能使用
// person = ["${key}": 'Guillaume']
assert person.containsKey('name')
assert !person.containsKey('key')
三、操作符
使用 intdiv()
执行整数除法, 除法/
会产生一个 double
或 BigDecimal
结果
注意 **
操作符返回的结果的类型
三元操作符可以简化
displayName = user.name ? user.name : 'Anonymous'
displayName = user.name ?: 'Anonymous'
1、对象操作符
1.1、安全导航操作符
防止访问空对象异常
上代码
def person = null
//访问空对象的name返回null
def name = person?.name
assert name == null
1.2、直接访问字段操作符
我们调用对象的 someObj.someProp
时,实际上执行的是 someObj.getSomeProp
或 someObj.setSomeProp
方法,
如果我们想直接访问对象的字段时,我们可以使用 someObj.@someProp
例如:
class User {
public final String name
User(String name) { this.name = name}
String getName() { "Name: $name" }
}
def user = new User('Bob')
// 使用 user.name 等价于 user.getName()
assert user.name == 'Name: Bob'
assert user.@name == 'Bob'
1.3、Method pointer 操作符
使用 .&
来表示存储一个相关的方法到变量,以便日后调用它
这种用法可以将函数变为 Closure
对象
上代码
def str = 'example of method reference'
def fun = str.&toUpperCase
// 调用fun()方法,而不是fun变量
// fun 实际上是一个 groovy.lang.Closure 对象
def upper = fun()
assert upper == str.toUpperCase()
2、正则表达式操作符
2.1、Pattern操作符
// 创建一个 java.util.regex.Pattern 对象
def p = ~/foo/
assert p instanceof Pattern
2.2、Find操作符
def text = "some text to match"
// =~ 创建一个 matcher,其类型为 java.util.regex.Matcher
def m = text =~ /match/
assert m instanceof Matcher
if (!m) {
throw new RuntimeException("Oops, text not found!")
}
2.3、Match操作符
// 使用 ==~ 直接判断是否match到
m = text ==~ /match/
assert m instanceof Boolean
if (m) {
throw new RuntimeException("Should not reach that point!")
}
3、其他操作符
3.1、 Spread operator
*.
操作符,用于索引集合内所有元素的属性,形成一个新的集合
例如
class Car {
String make
String model
}
def cars = [
new Car(make: 'Peugeot', model: '508'),
new Car(make: 'Renault', model: 'Clio')]
def makes = cars*.make
assert makes == ['Peugeot', 'Renault']
该操作符是 null-safe
的,这意味着,当集合内的对象为 null
时,它在相应的位置也是 null
,而不是抛出异常
cars = [
new Car(make: 'Peugeot', model: '508'),
null,
new Car(make: 'Renault', model: 'Clio')]
assert cars*.make == ['Peugeot', null, 'Renault']
assert null*.make == null
*.
操作符可以应用于任何实现了 Iterable
接口的对象
3.1.1、Spreading method arguments
有时你的方法需要多个参数。
此时,你可以使用 spread 操作符来调用方法。
例如
int function(int x, int y, int z) {
x*y+z
}
def args = [4,5,6]
assert function(*args) == 26
// 设置可以
args = [4]
assert function(*args,5,6) == 26
3.1.2、Spread list elements
def items = [4,5]
def list = [1,2,3,*items,6]
assert list == [1,2,3,4,5,6]
3.1.3、Spread map elements
def m1 = [c:3, d:4]
def map = [a:1, b:2, *:m1]
assert map == [a:1, b:2, c:3, d:4]
3.2、 Range operator
..
操作符
例子
def range = 0..5
assert (0..5).collect() == [0, 1, 2, 3, 4, 5]
assert (0..<5).collect() == [0, 1, 2, 3, 4]
// groovy.lang.Range 对象实现了 List接口
assert (0..5) instanceof List
assert (0..5).size() == 6
3.3、 Spaceship operator
<=>
操作符等价于 compareTo
方法
上代码
assert (1 <=> 1) == 0
assert (1 <=> 2) == -1
assert (2 <=> 1) == 1
assert ('a' <=> 'z') == -1
3.4、 Subscript operator
[index]
等价于 getAt
或 putAt
上代码
def list = [0,1,2,3,4]
assert list[2] == 2
list[2] = 4
assert list[0..2] == [0,1,4]
list[0..2] = [6,6,6]
assert list == [6,6,6,3,4]
这意味着,你可以重写或实现 putAt
和 getAt
方法,然后就可以调用你的对象的下标方法
3.5、 Membership operator
in
操作符 等价于 isCase
方法。
在List的上下文中,等价于 contains
方法
3.6、 Identity operator
在Groovy中, ==
等价于 equals
如果你想执行Java中的 ==
,使用 is
方法
上代码
def list1 = ['Groovy 1.8','Groovy 2.0','Groovy 2.3']
def list2 = ['Groovy 1.8','Groovy 2.0','Groovy 2.3']
assert list1 == list2
assert !list1.is(list2)
3.7、 Coercion operator
当你使用如下的方式强制类型时,会产生一个 ClassCastException
运行时错误
Integer x = 123
// 抛出 ClassCastException 异常
String s = (String) x
你可以使用 as
操作符
Integer x = 123
String s = x as String
当一个对象被强制为某一个类型时,除非目标类型和源类型相同,否则会产生一个新对象
强制类型的规则取决于源类型和目标类型,并且有可能失败。
自定义的 转换规则 可以通过 asType
方法实现
3.8、 Diamond operator
<>
操作符是一个纯语法糖操作符,用于支持与Java 7中同名操作符的兼容性。
3.9 Call operator
()
操作符 等价于 call
方法。
只要实现了 call
方法,你就可以省略 .call
部分,例如
class MyCallable {
int call(int x) {
2*x
}
}
def mc = new MyCallable()
assert mc.call(2) == 4
assert mc(2) == 4
4、操作符重载
image.png四、程序结构
默认的imports:
import java.lang.*
import java.util.*
import java.io.*
import java.net.*
import groovy.lang.*
import groovy.util.*
import java.math.BigInteger
import java.math.BigDecimal
带别名的import
import java.util.Date
import java.sql.Date as SQLDate
Date utilDate = new Date(1000L)
SQLDate sqlDate = new SQLDate(1000L)
1. Scripts versus classes
Groovy 支持 script 和 classes
1.1 Groovy编译器会为你将 script 编译到 classes
它会将 脚本的body 拷贝到一个run
方法中。
例如,编译后的类可能是下面的样子:
import org.codehaus.groovy.runtime.InvokerHelper
class Main extends Script {
def run() {
println 'Groovy world!'
}
static void main(String[] args) {
InvokerHelper.runScript(Main, args)
}
}
如果你在脚本中声明了方法,那么方法也会被编译。
所以,在脚本中,你可以不用在意方法声明的位置
1.2 变量
// 使用类型修订符声明的变量是 local variable。
// 它会被声明在 run 方法中,并且不被外部可见
int x=1
// 直接定义变量会进入 script binding。binding对外部可见
y=2
// 如果你想将变量作为类变量,使用 @Field Annotation
import groovy.transform.Field
@Field def x=1
例如:
def x
String line() {
"="*x
}
x=3
assert "===" == line()
x=5
assert "=====" == line()
上面的代码会运行出错,这是因为,编译后的代码是
class MyScript extends Script {
String line() {
"="*x
}
public def run() {
def x
x=3
assert "===" == line()
x=5
assert "=====" == line()
}
}
可以看到 变量x 在run方法内部,line方法无法访问到 变量x
你可以使用 @Field
Annotation
import groovy.transform.Field
@Field def x
String line() {
"="*x
}
x=3
assert "===" == line()
x=5
assert "=====" == line()
这样编译后的代码是
class MyScript extends Script {
def x
String line() {
"="*x
}
public def run() {
x=3
assert "===" == line()
x=5
assert "=====" == line()
}
}
五、面向对象
获取对象的 properties
class Person {
String name
int age
}
def p = new Person()
assert p.properties.keySet().containsAll(['name', 'age'])
println p.properties
// 输出 [class:class Person, age:0, name:null]
1、 Traits
Traits 是一种语言结构形式,它允许
- 行为的组合
- 运行时的接口实现
- 行为覆盖
- 兼容静态类型检查和编译
它可以被视为 接口
,并且包含了默认实现和状态。
1.1、 在Traits中声明方法
例子
trait FlyingAbility {
String fly() { "I'm flying!" }
}
class Bird implements FlyingAbility {}
def b = new Bird()
assert b.fly() == "I'm flying!"
1.1.1 在Traits中声明抽象方法
trait Greetable {
abstract String name()
String greeting() { "Hello, ${name()}!" }
}
class Person implements Greetable {
// 必须实现该抽象方法
String name() { 'Bob' }
}
def p = new Person()
assert p.greeting() == 'Hello, Bob!'
1.1.2 在Traits中声明private方法
在Traits中声明的private方法在外部不可访问
1.2 在Traits中this的含义
this
代表实现类的实例
例如
trait Introspector {
def whoAmI() { this }
}
class Foo implements Introspector {}
def foo = new Foo()
assert foo.whoAmI().is(foo)
...
1.3 陷阱
我们已经看到了 Traits是有状态的。
Traits可以定义 字段或 properties,但是当类实现Traits,它会在per-Traits基础上获取这些字段或 properties
如下面的例子
trait IntCouple {
int x = 1
int y = 2
int sum() { x+y }
}
class BaseElem implements IntCouple {
int f() { sum() }
}
def base = new BaseElem()
assert base.f() == 3
class Elem implements IntCouple {
int x = 3
int y = 4
int f() { sum() }
}
def elem = new Elem()
assert elem.f() == 3
对于 BaseElem 来说, base.f()==3
这没问题
但是对于 Elem , elem.f()==3
这是怎么回事?
这是因为 sum 函数 访问的是 trait中的字段,如果你想使用 实现类中的值,你需要使用 getters 和 setters
如下所示
trait IntCouple {
int x = 1
int y = 2
int sum() { getX()+getY() }
}
class Elem implements IntCouple {
int x = 3
int y = 4
int f() { sum() }
}
def elem = new Elem()
assert elem.f() == 7
六、Closures
1、 语法
1.1 定义closure
{ [closureParameters -> ] statements }
下面都是合法的定义
{ item++ }
{ -> item++ }
// 默认变量名称为 it
{ println it }
{ it -> println it }
{ name -> println name }
{ String x, int y ->
println "hey ${x} the value is ${y}"
}
{ reader ->
def line = reader.readLine()
line.trim()
}
closure可以作为一个变量,其类型为 groovy.lang.Closure
调用 closure:
def code = { 123 }
assert code() == 123
assert code.call() == 123
def isOdd = { int i -> i%2 != 0 }
assert isOdd(3) == true
assert isOdd.call(2) == false
def isEven = { it%2 == 0 }
assert isEven(3) == false
assert isEven.call(2) == true
2、Delegation strategy (代理策略)
2.1 Groovy closures vs lambda表达式
它们不一样
2.2 Owner、delegate and this
this
对应于声明 closure 的类对象,等价于使用 getThisObject()
owner
对应于声明 closure 的对象,它可能是一个类或一个closure,等价于使用 getOwner()
delegate
对应于调用closure的对象,等价于是 getDelegate()
delegate
可以被更改为任意对象。
例如
class Person {
String name
}
class Thing {
String name
}
def p = new Person(name: 'Norman')
def t = new Thing(name: 'Teapot')
def upperCasedName = { delegate.name.toUpperCase() }
upperCasedName.delegate = p
assert upperCasedName() == 'NORMAN'
upperCasedName.delegate = t
assert upperCasedName() == 'TEAPOT'
2.3、 Delegation strategy
在一个closure中,当一个 property 没有显式指定给某个receiver时,此时就引入了 delegation strategy
例如
class Person {
String name
}
def p = new Person(name:'Igor')
// name 没有关联的变量
def cl = { name.toUpperCase() }
// 我们可以更改 cl的delegate为Person对象
cl.delegate = p
// 然后该方法就可以成功调用
assert cl() == 'IGOR'
上面的代码能成功执行的原因是 name 属性在 delegate 对象上透明地解析!
closure实际上定义了多种 resolution strategies 供你选择:
- Closure.OWNER_FIRST : 这是默认策略,优先使用owner的属性/方法
- Closure.DELEGATE_FIRST :优先使用 delegate的属性/方法
- Closure.OWNER_ONLY
- Closure.DELEGATE_ONLY
- Closure.TO_SELF : 只使用 this 对象的属性/方法,这一般是在自定义Closure的实现中才会用到
例子:
class Person {
String name
def pretty = { "My name is $name" }
String toString() {
pretty()
}
}
class Thing {
String name
}
def p = new Person(name: 'Sarah')
def t = new Thing(name: 'Teapot')
assert p.toString() == 'My name is Sarah'
p.pretty.delegate = t
// 虽然更改了 delegate,但是不会改变 closure 的执行结果
// 因为默认的 策略是 Closure.OWNER_FIRST, 他仍然会使用 owner的属性
assert p.toString() == 'My name is Sarah'
// 更改策略后,结果就会发生变化
p.pretty.resolveStrategy = Closure.DELEGATE_FIRST
assert p.toString() == 'My name is Teapot'
3、 Functional programming
3.1 Currying
Groovy 中的 currying 允许你设置Closure中的一些参数的值,并返回一个新的closure以便接收更少的 参数
3.1.1 left Currying
上代码
def nCopies = { int n, String str -> str*n }
def twice = nCopies.curry(2)
assert twice('bla') == 'blabla'
assert twice('bla') == nCopies(2, 'bla')
3.1.2 right Currying
上代码
def nCopies = { int n, String str -> str*n }
def blah = nCopies.rcurry('bla')
assert blah(2) == 'blabla'
assert blah(2) == nCopies(2, 'bla')
3.1.3 Index based currying
上代码
def volume = { double l, double w, double h -> l*w*h }
// 设置第2个参数(index从0开始)为2d
def fixedWidthVolume = volume.ncurry(1, 2d)
assert volume(3d, 2d, 4d) == fixedWidthVolume(3d, 4d)
// 从第2个参数开始,设置多个参数的值
def fixedWidthAndHeight = volume.ncurry(1, 2d, 4d)
assert volume(3d, 2d, 4d) == fixedWidthAndHeight(3d)
3.2 Memorization
上代码
def fib
fib = { long n -> n<2?n:fib(n-1)+fib(n-2) }
assert fib(15) == 610 // slow!
// 因为计算 fib(15) 需要 fib(14) 和 fib(13)
// 计算 fib(14) 又需要 fib(13) 和 fib(12)
fib = { long n -> n<2?n:fib(n-1)+fib(n-2) }.memoize()
assert fib(25) == 75025 // fast!
// cache 使用 参数的实际值作为key .
// 这意味着当你使用非包装类型或原始类型的时使用 memoize() 时要注意
3.3 Composition
上代码
def plus2 = { it + 2 }
def times3 = { it * 3 }
def times3plus2 = plus2 << times3
assert times3plus2(3) == 11
assert times3plus2(4) == plus2(times3(4))
def plus2times3 = times3 << plus2
assert plus2times3(3) == 15
assert plus2times3(5) == times3(plus2(5))
// reverse composition
assert times3plus2(3) == (times3 >> plus2)(3)
3.3 Trampoline
递归算法经常被 stack
限制。
例如经常会收到 StackOverflowException
异常
Closure被包装在一个 TrampolineClosure
对象中。
被调用时, a trampolined Closure 会调用原始的 Closure 并等待其结果
如果调用的结果是另一个 TrampolineClosure
实例,则将再次调用这个Closure,直到返回 Closure 之外的值。
这个值将称为 trampoline 的最终结果,这样就实现了 serially call, 而不是 filling the stack
例子:
def factorial
factorial = { int n, def accu = 1G ->
if (n < 2) return accu
factorial.trampoline(n - 1, n * accu)
}
factorial = factorial.trampoline()
assert factorial(1) == 1
assert factorial(3) == 1 * 2 * 3
assert factorial(1000) // == 402387260.. plus another 2560 digits
七、Semantics
1、Statements
1.1 变量定义
def o
def
是类型名称的替代。
在声明变量时,使用 def
表明你不关心变量的类型,你可以认为 def
是 Object
的别名
在声明变量时,你可以必须类型名称或使用 def
,这是为了使Groovy解析器可以检测到这是声明变量
1.2 变量赋值
// 多个变量同时赋值
def (a,b,c) = [10,20,'foo']
assert a == 10 && b == 20 && c == 'foo'
def (int i, String j) = [10, 'foo']
assert i == 10 && j == 'foo'
def nums = [1, 3, 5]
def a, b, c
(a, b, c) = nums
assert a == 1 && b == 3 && c == 5
def (_, month, year) = "18th June 2009".split()
assert "In $month of $year" == 'In June of 2009'
// 如果左手边的变量多的话,多余的变量不会被赋值
def (a, b, c) = [1, 2]
assert a == 1 && b == 2 && c == null
// 如果右手边的值多的话,多余的值会被忽略
def (a, b) = [1, 2, 3]
assert a == 1 && b == 2
任何的对象只要实现了 getAt
方法,都可以用于多变量赋值
例如
@Immutable
class Coordinates {
double latitude
double longitude
double getAt(int idx) {
if (idx == 0) latitude
else if (idx == 1) longitude
else throw new Exception("Wrong coordinate index, use 0 or 1")
}
}
def coordinates = new Coordinates(latitude: 43.23, longitude: 3.67)
def (la, lo) = coordinates
assert la == 43.23
assert lo == 3.67
1.3 控制语句
1.3.1 条件语句
Java中的 if / else
Java中的 switch / case
Groovy 中的 switch 可以处理任意类型的switch value 和不同类型的match
例如
def x = 1.23
def result = ""
switch ( x ) {
case "foo":
result = "found foo"
// lets fall through
case "bar":
result += "bar"
case [4, 5, 6, 'inList']:
result = "list"
break
case 12..30:
result = "range"
break
// 当变量是 instanceof Integer 时匹配
case Integer:
result = "integer"
break
case Number:
result = "number"
break
// toString() representation of x matches the pattern?
case ~/fo*/:
result = "foo regex"
break
// Closure 返回结构为true时匹配
case { it < 0 }: // or { x < 0 }
result = "negative"
break
default:
result = "default"
}
assert result == "number"
1.3.2 循环结构
Java中标准的 for
Groovy 中的 for
例子
// iterate over a range
def x = 0
for ( i in 0..9 ) {
x += i
}
assert x == 45
// iterate over a list
x = 0
for ( i in [0, 1, 2, 3, 4] ) {
x += i
}
assert x == 10
// iterate over an array
def array = (0..4).toArray()
x = 0
for ( i in array ) {
x += i
}
assert x == 10
// iterate over a map
def map = ['abc':1, 'def':2, 'xyz':3]
x = 0
for ( e in map ) {
x += e.value
}
assert x == 6
// iterate over values in a map
x = 0
for ( v in map.values() ) {
x += v
}
assert x == 6
// iterate over the characters in a string
def text = "abc"
def list = []
for (c in text) {
list.add(c)
}
assert list == ["a", "b", "c"]
1.3.3 assertion
assert [left expression] == [right expression] : (optional message)
2、表达式
2.1 GPath表达式
GPath
是Groovy集成的路径表达式,它可以用于标识结构化数据的nested parts
GPath
一般用于处理XML,但是它可以应用于任意的对象
例如:
-
a.b.c
对于XML来说,会返回 a中的b中的c元素 -
a.b.c
对于POJOs来说,会返回a.getB().getC()
2.1.1 Object navigation
void aMethodFoo() { println "This is aMethodFoo." }
assert ['aMethodFoo'] == this.class.methods.name.grep(~/.*Foo/)
// this.class.methods.name.grep(~/.*Foo/) 等价于下面的代码
List<String> methodNames = new ArrayList<String>();
for (Method method : this.getClass().getMethods()) {
methodNames.add(method.getName());
}
// 后面再用grep 过滤
2.1.2 GPath for XML navigation
3、 Promotion and coercion
3.1 Closure to type coercion
3.1.1 将Closure指定为SAM type
SAM type是指 Single Abstract Method,即只定义了一个抽象方法的类型。
这包括如下
// 单方法的接口
interface Predicate<T> {
boolean accept(T obj)
}
// 只有一个抽象方法的抽象类
abstract class Greeter {
abstract String getName()
void greet() {
println "Hello, $name"
}
}
// 你可以使用 as 操作符将Closure converted into a SAM type
Predicate filter = { it.contains 'G' } as Predicate
assert filter.accept('Groovy') == true
Greeter greeter = { 'Groovy' } as Greeter
greeter.greet()
// 甚至你可以省略 as Type
Predicate filter = { it.contains 'G' }
assert filter.accept('Groovy') == true
Greeter greeter = { 'Groovy' }
greeter.greet()
// 这意味着你也可以使用 method pointers
boolean doFilter(String s) { s.contains('G') }
Predicate filter = this.&doFilter
assert filter.accept('Groovy') == true
Greeter greeter = GroovySystem.&getVersion
greeter.greet()
3.1.2 Calling a method accepting a SAM type with a closure
调用接收一个SAMtype作为参数的方法,然后使用 Closure来代替 SAM type
假设如下方法
public <T> List<T> filter(List<T> source, Predicate<T> predicate) {
source.findAll { predicate.accept(it) }
}
// 你可以使用如下方式调用
assert filter(['Java','Groovy'], { it.contains 'G'} as Predicate) == ['Groovy']
// 甚至可以省略 as Type
assert filter(['Java','Groovy']) { it.contains 'G'} == ['Groovy']
3.1.3 Closure to arbitrary type coercion
除了SAM types,Closure还可以被强制为任意的类型
例如
interface FooBar {
int foo()
void bar()
}
def impl = { println 'ok'; 123 } as FooBar
assert impl.foo() == 123
impl.bar()
// 除了可以作为接口外,也可以作为类
class FooBar {
int foo() { 1 }
void bar() { println 'bar' }
}
def impl = { println 'ok'; 123 } as FooBar
assert impl.foo() == 123
impl.bar()
3.2 Map to type coercion
一般来说,使用一个单独的closure实现一个接口或类 不是一个好方式。
所以,groovy允许我们将一个map强制为一个接口或类。map的keys被解释为方法名称,values被解释为方法实现。
例如
def map
map = [
i: 10,
hasNext: { map.i > 0 },
next: { map.i-- },
]
def iter = map as Iterator
// 当调用map中不存在的方法时,会抛出 MissingMethodException 或 UnsupportedOperationException 异常
// 如下所示
interface X {
void f()
void g(int n)
void h(String s, int n)
}
x = [ f: {println "f called"} ] as X
x.f() // method exists
x.g() // MissingMethodException here
x.g(5) // UnsupportedOperationException here
3.3 字符串到Enum强制转换
上代码
enum State {
up,
down
}
State st = 'up'
assert st == State.up
// 字符串不存在于enum中时,会抛出 IllegalArgumentException 运行时异常
State st = 'not an enum value'
3.4 自定义的类型强转
实现 asType
方法
上代码
class Polar {
double r
double phi
def asType(Class target) {
if (Cartesian==target) {
return new Cartesian(x: r*cos(phi), y: r*sin(phi))
}
}
}
class Cartesian {
double x
double y
}
def sigma = 1E-16
def polar = new Polar(r:1.0,phi:PI/2)
def cartesian = polar as Cartesian
assert abs(cartesian.x-sigma) < sigma
也可以将 asType 放在 Polar 类定义外面,使用 metaClass 扩展 ( 个人不建议, 因为这是全局的, 但是可以只设置某个具体对象的 metaClass 只对该对象有效)
Polar.metaClass.asType = { Class target ->
if (Cartesian==target) {
return new Cartesian(x: r*cos(phi), y: r*sin(phi))
}
}
3.5 Class literals vs variables and the as operator
interface Greeter {
void greet()
}
// Greeter is known statically, 此时使用 as
def greeter = { println 'Hello, Groovy!' } as Greeter
greeter.greet()
Class clazz = Class.forName('Greeter')
// 此时不能使用 as, 因为 as 关键字只工作于 class literals
greeter = { println 'Hello, Groovy!' } as clazz
// 取而代之的,你可以使用 asType 方法
greeter = { println 'Hello, Groovy!' }.asType(clazz)
greeter.greet()
4、Optionality
4.1 可选的括号() parentheses
调用方法时省略括号: 如果方法至少有一个参数,并且没有歧义,那么可以省略括号
例如
println 'Hello World'
def maximum = Math.max 5, 10
// 下面的语句则不能省略括号
println()
println(Math.max(5, 10))
4.2 可选的 分号 semicolons
4.3 可选的 return 关键字
4.4 可选的 public 关键字
5、Groovy Truth
true 表示
- 布尔true、
- 集合非空、
- matcher匹配、
- 迭代器和枚举的hasNext为true、
- map非空、
- 字符串长度不为0、
- 数字非0、
- 对象非null、
- 对象的asBoolean()方法返回true
6、类型
6.1 可选类型
使用 def
动态推断对象类型
6.2 静态类型检查
使用 @groovy.lang.TypeChecked
注解类和方法 以激活类型检查
当激活类型检查后,编译器会执行更多的工作:
(1) 类型推断被激活,这意味着即使你在局部变量上使用def,类型检查器也能够从assignments中推断变量的类型
(2) 方法调用在编译时解析,这意味着如果方法没有在类上声明,编译器会抛出一个错误
(3) 一般来说,所有用于以静态语言查找的编译时错误都会显示:未找到方法,未找到属性,方法调用的不兼容类型,编号精度错误,...
使用 @TypeChecked(TypeCheckingMode.SKIP)
注解 跳过类型检查
6.2.1 类型检查assignments
一个A类型的对象o可以被指定为T类型的变量,只要满足下列条件:
(1) 对象类型A 和 变量类型T 一样: 例如 Date now = new Date()
(2) 变量类型T是 String、boolean、Boolean或Class
例如
String s = new Date() // implicit call to toString
Boolean boxed = 'some string' // Groovy truth
boolean prim = 'some string' // Groovy truth
Class clazz = 'java.lang.String' // class coercion
(3) 对象o是null,且变量类型T不是原始类型(例如int)
(4) 变量类型T是 array,而且对象类型A也是array,并且A的组成类型可以被指定为T的组成类型
例如 int[] i = new int[4] ; int[] i = [1,2,3]
(5) 变量类型T 是 对象类型A的 superclass 或 变量类型T 是 对象类型A实现的接口
(6) 变量类型T 或对象类型A 是原始类型和它们的包装类型
(7) 变量类型T 是一个 SAM-type , 对象类型A是 groovy.lang.Closure
或其子类
例如
Runnable r = { println 'Hello' }
interface SAMType {
int doSomething()
}
SAMType sam = { 123 }
assert sam.doSomething() == 123
abstract class AbstractSAM {
int calc() { 2* value() }
abstract int value()
}
AbstractSAM c = { 123 }
assert c.calc() == 246
(8) 变量类型T 或对象类型A 是Number的子类并且它们满足相应的条件
6.2.2 List and map constructors
除了上面的规则之外,如果 assignment 在类型检查模式下被认为是无效的,那么如果满足以下条件,则可以将 list literal 或 map literal A 指派给 T类型的变量:
(1) assignment 是一个变量声明,并且A是一个list,并且T的 constructor的参数匹配list中的元素的类型
(2) assignment 是一个变量声明,并且A是一个map,并且T有一个no-arg constructor,并且map中的每个key都是其属性
例如
@groovy.transform.TupleConstructor
class Person {
String firstName
String lastName
}
Person classic = new Person('Ada','Lovelace')
// You can use a "list constructor":
Person list = ['Ada','Lovelace']
// or a "map constructor":
Person map = [firstName:'Ada', lastName:'Lovelace']
// map 中不能包含多余的key,否则会在编译时报异常
网友评论