Groovy是一种基于JVM的敏捷开发语言,结合了Python、Ruby和Smalltalk的许多强大的特性。Groovy在语法上支持动态类型、闭包等新一代语言特性,它能够无缝集成所有已经存在的Java类库,即支持面向对象也支持面向过程编程。
变量类型分为基本类型和对象类型,但是groovy中所有的类型都是对象类型,基本类型其实是包装类。变量可以使用强类型定义,也可以使用def方式的弱类型定义;强类型就是指定的类型,def定义的类型其实是声明成了Object类型。
double num = 10.0
// java.lang.Double
println num.class
def x = 10
// java.lang.Integer
println x.class
x = "Hello groovy"
// java.lang.String
println x.class
String类和Java中的String类基本上相同,不在详细介绍。GString是Groovy里添加的新字符串类型,功能很强大。字符串定义使用单引号形式的字符串,还有一种三引号格式的字符串可以保留字符串格式,这两种字符串都不能被修改。
def name = 'str'
// java.lang.String
println name.class
def tripleName = '''three
single
string'''
// 打印字符串保留了换行
println tripleName
// java.lang.String
println tripleName.class
还有一种双引号的字符串,默认情况是String类型,如果包含参数模板那么就会变成GString类型。在开发过程中不必特意转换String和GString,编译器会帮助开发者做转换操作。
def text = "Android"
// java.lang.String
println text.class
def sayHello = "Hello: $text"
println sayHello
// GStringImpl
println sayHello.class
def product = "The product of 3 * 4 = ${3 * 4}"
println product
// GStringImpl
println product.class
// GStringImpl自动转换成String,不会报错,能成功运行
def result = echo(product)
println result
// 可以接受String类型,也可以接受GStringImpl类型
String echo(String message) {
return message
}
GString包含了String中所有的方法,还有DefaultGroovyMethods是Groovy针对Java中所有类型扩展的新方法,最后还包含了StringGroovyMethods继承了DefaultGroovyMethods并重写了一些方法使之更加适合字符串操作。
def name = 'string'
println name.center(10, '$')
println name.padLeft(10, '$')
println name.padRight(10, '$')
println name[2]
println name[2..5]
def str = 'org.codehaus.groovy:groovy-all:2.4.9'
def (group, name, version) = str.split(":")
println group
println name
println version
输出:
$$string$$
$$$$string
string$$$$
r
ring
org.codehaus.groovy
groovy-all
2.4.9
Groovy中的判定结构if和Java中的基本一致,switch语句则比Java中的要强大很多,switch支持各种类型的判断,不再局限于整数、字符串和enum类型了。循环for和while语句和Java中使用方式基本上一致,不过针对List和Map,Groovy的for循环采用了in操作符来做遍历。
def x = 1
switch (x) {
case 2:
break
case 'Hello':
break
case Integer.class:
println x
break
case [1, 3, 5]:
break
case ['k1': 'v1', 'k2' : 'v2' ]:
break
default:
break
}
// 1
for (it in [1, 3, 5]) {
println it
}
// 1
// 3
// 5
for (it in ['k1': 'v1', 'k2' : 'v2' ]) {
println it.key + it.value
}
// k1v1
// k2v2
闭包就是一个代码块,使用{}包裹,简单的闭包定义就是大括号加里面的代码块,可以通过闭包的call方法或者加()直接调用,类似于函数调用。闭包如果只有一个变量,可以在声明中忽略这个参数,直接在使用时用it关键字。
def call = { println 'Hello World' }
call.call()
call()
def closure = { String name -> println "Hello $name" }
// def closure = { println "Hello $it" }
closure.call("World")
closure("World")
// Hello World
闭包一定会有返回值,如果闭包最后没有return语句,那么返回值就是最后的表达式返回值,如果表达式返回void,那么闭包返回的就是null。
def closure = { println "Hello $it" }
def result = closure("World")
// null
println result
闭包的三个重要变量this,owner,delegate,其中this代表定义闭包的这个类;owner代表闭包定义处的类,也可以代表定义处的对象,因为闭包可以定义在闭包里,这是嵌套闭包的owner就是外部的闭包对象;delegate可以是任意的值,默认情况下就是owner的值。
def closure = {
println "Closure this: " + this
println "Closure owner: " + owner
println "Closure delegate: " + delegate
}
closure()
def call = {
def myclosure = {
println "MyClosure this: " + this
println "MyClosure owner: " + owner
println "MyClosure delegate: " + delegate
}
myclosure()
}
call()
总之,this、owner、delegate在类或者方法中定义的时候它们默认都是一样的,都是定义它们所在的类或类对象,如果在闭包中定义闭包那么this还是当前定义的类或类对象,而owner和delegate会指向外部闭包对象;owner和delegate默认情况下都是一样的,但是用户手动修改delegate之后,owner和delegate就会不一致的,还有this和owner是无法修改的。
class Person {
String name
def print = {
println "Hello $name"
}
String toString() {
print()
}
}
class User {
String name
}
def person = new Person(name: 'Person')
def user = new User(name: 'User')
// Hello Person
person.toString()
person.print.delegate = user
person.print.resolveStrategy = Closure.DELEGATE_FIRST
// Hello User
person.toString()
闭包委托策略:
策略值 | 意义 |
---|---|
OWNER_FIRST | 首先从owner对象上查找 |
DELEGATE_FIRST | 首先从delegate对象上查找 |
OWNER_ONLY | 只从owner上查找 |
DELEGATE_ONLY | 只从delegate对象上查找 |
TO_SELF | 只从当前闭包对象上查找 |
List的定义很简单只需要使用中括号并且用逗号分隔每个元素就可以了。不过这种定义方式同样适用于数组类型,可以通过as关键字或者直接在定义变量的时候制定数组类型来定义数组。
def list = [1, 2, 3, 4, 5]
println list.toListString()
println list.class
int[] intArr = [1, 2, 3, 4, 5]
println intArr.toString()
println intArr.class
// [1, 2, 3, 4, 5]
// class java.util.ArrayList
def arr = [1, 2, 3, 4, 5 ] as int[]
println arr.class
// [1, 2, 3, 4, 5]
// class [I
// class [I
通过上面的示例代码可以看出List默认情况下是ArrayList类型,Groovy里的列表对象底层实现其实是数组实现。和Java8类似Groovy的List对象也包含了各种函数式编程扩展方法。
def list = [1, 2, 3, 4, 5]
// 遍历每个元素
list.each {
println it
}
// 12345
// 找到第一个符合条件的元素
println list.find { it % 2 == 0 }
// 2
// 找到所有符合条件的元素
println list.findAll {
it % 2 == 0
}
// [2, 4]
// 是否存在某个元素符合该条件
println list.any { it % 2 == 0 }
// true
// 列表中每个元素都符合该条件
println list.every { it % 2 == 0 }
// false
// 根据条件将列表分组
println list.groupBy {
it % 2 == 0
}
// [false:[1, 3, 5], true:[2, 4]]
Map的定义也是使用中括号加逗号分隔元素,不过Map的元素是key:value这种类型的,如果key是不可变的String类型,那么可以直接省略两边的引号。
def map = [key1 : 'user1', key2 : 'user2', key3: 'user3']
println map['key1']
println map.key1
// user1
上面的定义使用起来非常简单,和List类似,Map也扩展了很多有用的函数式变成接口。
def map = [key1 : 'user1', key2 : 'user2', key3: 'user3']
map.each {
println "${it.key} = ${it.value}"
}
// key1 = user1
// key2 = user2
// key3 = user3
println map.find {
it.key == 'key2'
}
// key2=user2
println map.findAll {
it.key.startsWith('key')
}
// [key1:user1, key2:user2, key3:user3]
map.eachWithIndex { Map.Entry entry, int i ->
println "$i ${entry.key} = ${entry.value}"
}
// 0 key1 = user1
// 1 key2 = user2
// 2 key3 = user3
println map.any {
it.value == 'user2'
}
// true
println map.every {
it.value == 'user2'
}
// false
println map.groupBy {
it.value
}
// [user1:[key1:user1], user2:[key2:user2], user3:[key3:user3]]
Range范围是List的一种实现,不过它主要是针对int类型的数据,可以使用..简单地定义范围对象,还可以在..前后添加符号声明是否包含边界值。
def range = 1..10
def range2 = 1..<10
range.each {
println it
}
// 1 2 3 4 .. 8 9 10
range2.each {
println it
}
// 1 2 3 .. 8 9
由于Range继承自List,对于List相关的扩展方法同样适用于Range,这里不再赘述。
Groovy的类接口定义和Java基本一致,不过Groovy的类可以不加访问控制权限,默认情况下都是public访问权限。
class Person {
String name
int age
String toString() {
return "name = $name, age = $age"
}
def execute(x, y, z) {
println "$x $y $z"
}
def getName() {
println("getName")
return name
}
}
def person = new Person(name: 'zhangsan', age: 30)
println person.toString()
println person.@name
// zhangsan
println person.name
// getName
// zhangsan
person.execute(x: 1, y:2, z: 3, 4, 5)
// [x:1, y:2, z:3] 4 5
Java8之前的interface都只能包含抽象方法,不能有方法实现,不过Java8为了lambda表达式增加了默认方法。Groovy的interface类似于Java8之前的接口,只有抽象接口方法;trait类似于Java8中的接口,既可以包含抽象接口也可以包含默认实现方法,它也可以用来实现。
trait Movable {
abstract void move()
String description() {
return "I can move"
}
}
class Car implements Movable {
@Override
void move() {
println "Moving ..."
}
}
def car = new Car()
car.move()
println car.description()
interface和闭包可以相互转换,不过这种转换是通过生成动态代理类来实现的,对于只有一个方法的接口,闭包内的call会直接调用这个接口两者可以实现无缝转换,对于有多个方法的接口如果无法与闭包做转换会抛出异常。
interface OnClickListener {
void onClick(str)
}
def func(OnClickListener listener) {
listener.onClick('Good morning')
}
OnClickListener l = {
println it
}
func(l)
println l.class
// Good morning
// class com.sun.proxy.$Proxy4
interface Transaction {
void before()
void doing()
void after()
}
def trans(Transaction transaction) {
transaction.before()
transaction.doing()
transaction.after()
}
Transaction transaction = {
println 'Good morning'
} as Transaction
trans(transaction)
println transaction.class
// Good morning
// Good morning
//Good morning
// class com.sun.proxy.$Proxy5
Groovy面向对象有一个强大的特性,也就是元编程,它能支持运行时动态添加类的属性和方法。默认情况下只在添加属性和方法的类中有效,可以通过调用ExpandoMetaClass.enableGlobally()使添加属性和方法全局有效。
class Car {
void move() {
println "Moving ..."
}
}
def car = new Car()
car.metaClass.start = {
-> println "start the engine"
}
car.metaClass.wheelCount = 4
car.move()
car.start()
println car.wheelCount
上面的MetaClass元编程能够在运行时动态添加属性和方法,这也就是为什么编译时调用对象不存在的方法也不报错的原因。如果用户没有为类添加需要的方法,如果类覆盖了methodMissing方法,那么这个方法就会被调用,如果类没有覆盖这个方法但是覆盖了invokeMethod方法,那么invokeMethod方法会被调用。如果以上两个方法都没有被覆盖,最后会在运行时跑出MethodMissingException异常并终止应用。