前言
Groovy是构建工具Gradle的核心语言,因此,本文为后续学习Gradle做基础。
简介
- Groovy是基于jvm虚拟机的一种动态脚本语言,完全兼容java,又在此基础上增加了很多动态类型和灵活的特性。
- 特性:支持动态/静态类型、支持DSL语法特性、无缝衔接Java等
- 面向对象
Groovy基础
1. 数据类型
基本数据类型 byte、short、int、long、float、double、char、boolean
包装类 String、Byte、Short、Integer、Long、Float、Double、Char、Boolean
2. 字符串
' '
:单引号字符串是java.lang.String类型,不支持表达式,作为字面常量。
" "
:双引号字符串在没有使用插值表达式情况下是java.lang.String类型,但如果有表达式使用的话,就是groovy.lang.GString类型。
''' '''
:三引号字符串表示多行字符串,不必将字符串分割成几块,也不必用连接符或换行符转义字符来将字符串跨行。
def groovy1 = 'groovy1'
def groovy2 = "groovy2"
def groovy3 = "groovy3 插入表达式->${groovy2}"
def groovy4 = '''groovy
2
3'''
println(groovy1) //输出:groovy1
println(groovy1.getClass()) //输出:class java.lang.String
println(groovy2) //输出:groovy2
println(groovy2.getClass()) //输出:class java.lang.String
println(groovy3) //输出:groovy3 插入表达式->groovy2
println(groovy3.getClass()) //输出:class org.codehaus.groovy.runtime.GStringImpl
println(groovy4) //输出:groovy
2
3
println(groovy4.getClass())//输出:class java.lang.String
3. 变量
使用 def 定义变量,动态类型放低了对类型的要求,使语言能够根据上下文来判定变量类型。
def str = 'str'
def num = 123
println str.getClass() //输出:class java.lang.String
println num.getClass() //输出:class java.lang.Integer
4. 安全导航操作符
检查引用是否空值。
def foo(str){
str?.reverse()
}
println foo('abc') //输出:cba
println foo(null) //输出:null
5. 集合类
5.1 List
def list = [1,2,3]
//打印集合
println(list) //输出:[1, 2, 3]
//集合类型
println list.getClass() //输出:class java.util.ArrayList
//根据下标获取对应位置的值
println list[0] //输出:1
//循环
list.each { print it } //输出:123
//取区间
println list[0..1] //输出:[1, 2]
5.2. Map
def maps = ['a': 'A', 'b': 'B', 'c': 'C']
//类型
println maps.getClass() //输出:class java.util.LinkedHashMap
//根据key得到value
println maps['a'] //输出:A
println maps.b //输出:B
//遍历
maps.each {println "key:$it.key value:$it.value" }
//输出:key:a value:A
key:b value:B
key:c value:C
5.3. Range
范围是一种特殊的列表,由序列中的第一个和最后一个值表示,Range可以是包含或排除。包含范围包括从第一个到最后一个的所有值,而独占范围包括除最后一个之外的所有值。也可以使用表达式来表示范围。
def range = 0..10
println range.getClass() //输出:class groovy.lang.IntRange
1..10 包含范围的示例
1..<10 独占范围的示例 (开区间)
'a'..'x' 范围也可以由字符组成
10..1 范围也可以按降序排列
6. 类与方法
构造函数 方法传参(默认值,合并 map,Object)
- 默认会生成 getter, setter 方法:
- 可以直接像使用成员变量的方法来自动判断调用 getter/setter。
- 使用 '.@' 直接访问变量,跳过默认的 getter/setter 方法调用。
- 如果没有指定具体参数的类型时,默认推断类型是 Object。
7. 函数与闭包
- Groovy 中可以理解为闭包就是可执行的代码块,或匿名函数。闭包在使用上与函数与许多共通之处,但是闭包可以作为一个函数的参数。
- 默认情况下,闭包能接收一个参数,且参数字段默认使用 it。
- 在箭头前面没有定义参数,这时候闭包不能传入参数。
- 可以定义多个接收的参数。
- 使用参数默认值,跟方法使用规则一样。
def pickEvent(max, block) {
for (int i = 2; i <= max; i += 2) {
block(i)
}
}
pickEvent(10) { println(it) }
输出:2 4 6 8 10
def foo = { it?.reverse() }
println(foo("123"))
输出:321
7.1 闭包委托
闭包是一个 Closure 对象
public abstract class Closure<V> extends GroovyObjectSupport implements Cloneable, Runnable, GroovyCallable<V>, Serializable {
//闭包中的关键对象
...
private Object delegate;
private Object owner;
private Object thisObject;
private int resolveStrategy;
...
}
class Test {
//闭包2
def closure2 = {
println "this is" + this
println "owner is" + owner
println "delegate is" + delegate
}
//闭包1
def closure1 = {
def closure2 = {
// this:Test@2b76ff4e,定义它的时候类的this,当它是static时就是class对象
println "this is" + this
// owner:Test$_closure1@8c3619e,定义它的时候的类的对象
println "owner is" + owner
// delegate:Test$_closure1@8c3619e,默认就是owner, 但是代理可以修改
println "delegate is" + delegate
}
closure2()
}
}
new Test().closure2()
输出: 同一对象
this isTest@b2070ef
owner isTest@b2070ef
delegate isTest@b2070ef
new Test().closure1()
输出: this与其他对象不同
this isTest@164103c3
owner isTest$_closure2@51105e92
delegate isTest$_closure2@51105e92
//使用委托
class Test2 {
def func() {
println "Test2 func"
}
}
def func() {
println "Script func"
}
def closure = {
func()
}
closure() //输出为:Script func
//切换代理类为Test2
closure.delegate = new Test2()
closure() //输出依旧是:Script func 原因:优先选择的是owner
//策略
//* @see groovy.lang.Closure#DELEGATE_FIRST
//* @see groovy.lang.Closure#DELEGATE_ONLY
//* @see groovy.lang.Closure#OWNER_FIRST
//* @see groovy.lang.Closure#OWNER_ONLY
//* @see groovy.lang.Closure#TO_SELF
//设置策略
closure.resolveStrategy=Closure.DELEGATE_ONLY
closure() //输出:Test2 func
8. Groovy动态特性
-
动态类型语言:动态类型语言是指在运行期间检查数据类型,也就是说,在用动态类型的语言编程时,可以不用给变量指定数据类型,该语言会在你第一次赋值给变量时,在内部将数据类型记录下来。
静态类型语言:静态类型语言的数据类型是在编译期间检查的,也就是说在写程序时要声明所有变量的数据类型,C/C++是静态类型语言的典型代表,其他的静态类型语言还有C#、Java等。
-
使用def定义变量,类型由运行时对其赋值的类型确定,通过这种能力,可以在运行时向类中注入行为,十分灵活,但相应的,如果如果类型不符的话会在运行时报错,所以编码时要更加注意,但也可以使用 @TypeChecked 强制编译检查。
class User { def username = 'zee' } def user = new User() println user.username.class //输出:class java.lang.String user.username = new Object() println user.username.class //输出:class java.lang.Object user.username = 123 println user.username.class //输出:class java.lang.Integer user.username = new User() println user.username.class //输出:class User
9. Groovy元编程
- MOP (元对象协议)全称:Meta Object Protocol。
- Java中可以通过反射,在运行时动态的获取类的属性,方法等信息,然后反射调用。但是没法直接做到往内中添加属性、方法和行为。( 需要通过动态字节码技术如ASM、javassist等技术来实现动态的修改class )。
- Groovy可以使用MOP进行元编程,我们可以基于应用当前的状态,动态的添加或者改变类的方法和行为。比如在某个Groovy类中并没有实现某个方法,这个方法的具体操作由服务器来控制,使用元编程,为这个类动态添加方法,或者替换原来的实现,然后可以进行调用。
9.1. 方法拦截
9.1.1. 实现GroovyInterceptable接口,重写invokeMethod来进行拦截。
class Target implements GroovyInterceptable {
def function() {
System.out.println 'target#function-invoke'
}
@Override
Object invokeMethod(String name, Object args) {
if (metaClass.invokeMethod(this, 'respondsTo', name, args)) {
System.out.println "$name begin invokeMethod"
metaClass.invokeMethod(this, name, args)
System.out.println "$name end"
}
}
}
new Target().function()
输出:
function begin invokeMethod
target#function-invoke
function end
9.1.2. 使用MetaClass拦截方法,覆盖invokeMethod方法。
使用类的 MetaClass,针对的是 class 对象,所有实例都会被影响。
使用具体实例的 MetaClass,只影响当前对象实例。
class Target implements GroovyInterceptable {
def function() {
System.out.println 'target#function-invoke'
}
}
def target = new Target()
//这里只拦截此对象的方法
target.metaClass.function = {
println 'target#function-invoke-metaClass'
}
target.function() //输出:target#function-invoke-metaClass
target.metaClass.invokeMethod = {
String name, Object args ->
println "$name invoke-metaClass11"
}
target.function() //输出:function invoke-metaClass11
//这里是拦截了此类的方法
Target.metaClass.invokeMethod = {
String name, Object args ->
println "$name invoke-metaClass22"
}
new Target().function() //输出:function invoke-metaClass22
9.2. MOP方法注入
方法注入:编写代码时知道想要添加到一个或多个类中的方法的名字。利用方法注入,可以动态地向类中添加行为。也可以向任意数目的类中注入一组实现某一特定功能的可服用方法,就像工具函数。
有以下几种方式:
9.2.1. 使用分类注入方法。
class TargetUtils {
static def newFunction(Target self) {
println "newFunction-invoke"
}
}
def target = new Target()
use(TargetUtils) {
target.newFunction()
}
//输出:newFunction-invoke
9.2.2. 使用ExpandoMetaClass注入方法。
class Target {
def function() {
System.out.println 'target#function-invoke'
}
}
def ext = new ExpandoMetaClass(Target)
ext.newFunction2={
println 'target#function-invoke-new2'
}
ext.initialize()
//这里也可以给对象的metaClass赋值,仅作用在对象上
Target.metaClass = ext
new Target().newFunction2()
//输出:target#function-invoke-new2
9.2.3. 直接使用类或实例的MetaClass注入方法,实际上最终操作的类型是ExpandoMetaClass。
Target.metaClass.newFun = {
println 'target#function-invoke-new'
}
new Target().newFun()
//输出:target#function-invoke-new
9.2.4. 使用Mixin注入方法。( 在类中可以使用多个Mixin )
class Helper{
def help(){
println "help"
}
}
@Mixin(Helper)
class Target {
def function() {
System.out.println 'target#function-invoke'
}
}
new Target().help()
//输出:help
class PersonA{ }
PersonA.mixin Helper
new PersonA().help()
//输出:help
9.3.MOP方法合成
方法合成:想在调用时动态地确定方法的行为。
Groovy 的 invokeMethod()、methodMissing() 和 GroovyInterceptable 对于方法合成非常有用。
9.3.1.使用methodMissing()合成方法。
class Person {
def work() { 'working...' }
def methodMissing(String name, args) {
System.out.println "methodMissing $name,$args"
if(name == 'play'){
"invoke method $name,$args"
}else{
throw new MissingClassException(name,Persion.class,args)
}
}
}
println new Person().play()
//输出: methodMissing play,[]
invoke method play,[]
9.3.2.使用ExpandoMetaClass合成方法。
Person.metaClass.methodMissing = { String name,args->
System.out.println "methodMissing $name,$args"
if(name == 'play'){
"invoke method $name,$args"
}else{
throw new MissingClassException(name,Persion.class,args)
}
}
println new Person().play()
//输出: methodMissing play,[]
invoke method play,[]
总结
到此,我们学习到了变量,字符串,集合(List、Map、Range),闭包,动态特性、元编程(方法拦截、合成、注入),后续学习Gradle时会使用到这些技巧,故基础要打好。