可以先大体了解一门语言,遇到不懂的地方时再来查找资料学习。

def和final

def和final用于定义可变变量和不可变变量。方法的定义使用def,如果方法前没有def就是一个方法调用而不是定义,由于有闭包的语法,这二者有时不太好区分。
如果定义变量时,不加def或者final,那么相当于定义了一个绑定到当前脚本的binding对象上的变量。

多态

和Java不同,groovy的多态和接口无关,当调用一个对象的方法时,groovy会在运行时尝试找到最匹配的方法进行调用,而不是像Java一样在编译时根据当前对象的静态类型确定调用的方法。虽然很多人说合法的java代码都是合法的groovy代码,但从这个区别中可以看出,原本在java中合理的代码语义,到groovy中可能会变成不符合预期的语义。

闭包

检查闭包参数数目和类型

可以使用maximumNumberOfParameters和parameterTypes两个属性检查闭包对象可接受的参数数目及类型。

this, owner, delegate

变量查找顺序是this > owner > delegate。
this是外围类引用,owner是外围作用域引用,可能是外围类或者闭包,delegate默认情况下和owner一样,但允许修改。通过修改delegate,可以实现fancy的dsl语法。通常要设置delegate需要注意创建闭包的副本,并在副本上设置delegate,但使用一个特殊的with方法,就可以完成:闭包副本创建、设置delegate、调用闭包三件事。如果之前没见过with,第一次见到这种方法时肯定是会懵逼的,尤其是官方文档对这个方法的背景没介绍清楚,就算读过文档也不一定能理解。
顺便一提,一般来说为闭包设置delegate前需要先复制闭包,即调用closure.clone方法,这是为了不修改原始闭包对象的delegate,避免打破外部预期。

无参数闭包

如果在闭包定义中不写参数列表和 -> ,并不意味着闭包不接受任何参数,而是意味着闭包接受单个参数,并且该参数会被隐式绑定到it变量。为了声明一个不接受任何参数的闭包,必须要明确使用 -> 和空参数列表:
noParamClosure = {-> 123}

字符串

GString

Groovy中创建字符串时可以使用单引号和双引号创建字符串,和其他脚本语言不同的一点是:groovy中用双引号创建的字符串有可能并不是一个普通的字符串,如果字符串中绑定了变量,那么创建结果将是一个GString类型的特殊字符串,这种字符串支持惰性求值,如果在这个字符串中绑定的变量内容发生了变化,那么字符串表达式的值也会随之改变。初次了解这一点会让人觉得比较吃惊,但是,get used to it!
如果想利用GString的动态求值特性,通常会在绑定变量时使用闭包来引用字符串,因为字符串是不可变对象,无法修改字符串内容,只好让闭包返回当前某个引用绑定的字符串来利用惰性求值:
def bindedStr = “hello, world?”
def myGString = “test ${-> bindedStr}”
bindedStr = “123”
在定义多行字符串时,groovy使用三个引号提供了heredoc支持,’’’表示普通字符串,”””表示可以绑定变量的字符串。

对Java字符串的增强

Groovy默默的为Java的String增加了很多便捷方法。
可以使用execute直接起进程调用一个命令,并获取命令的输出;
可以利用groovy重载的操作符完成合乎逻辑的操作;
其他groovy提供的便捷函数…

正则表达式

在字符串前面加一个~可以创建正则表达式Pattern。
字符串和Pattern中间写=~或者==~表示部分匹配和完全匹配。

集合

List

  • ArrayList创建:def arr = [1, 2, 3]
  • 数组下标操作符:arr[0]; arr[-2]; arr[1..2]; arr[-2..-1];
  • functional:each, find, findAll, collect, inject, any, every
  • 展开函数:flatten, 展开操作符 * 语法(spread operator)

    • arr*.size();
    • callFunc(*arr);

      Map

  • LinkedHashMap创建:def dict = [‘C++’: ‘Stroustrup’, ‘Java’: ‘Gosling’, ‘Lisp’: ‘McCarthy’];

  • 取出Map元素:数组下标操作符 - dict[‘C++’]、点操作符dict.’C++’; dict.Java;
    • 由于Map允许使用点操作符,取出key对应的value,导致和使用点操作符访问属性getter的语法冲突,因此Map对象无法使用点操作符访问getter,必须要写明getter方法调用。
  • functional: each, collect, find, findAll, any, every, groupBy
  • Map还可以用于传递具名参数和实现接口

GDK

对Object的扩展

  • debug支持:dump()返回对象的字符串描述、inspect()返回创建对象的表达式
  • with / identity:方便设置闭包的delegate为对象自身,然后调用闭包
  • 改进的sleep:Java版本的sleep要求我们必须try-catch中断异常,大部分情况下catch语句块中都是空的,groovy则帮我们处理了中断异常,仅在需要时传入一个闭包来指定中断异常处理函数
  • 间接访问属性:可以使用[]利用运行时计算出来的字符串作为属性名访问或者修改属性
  • 间接调用方法:可以使用invokeMethod利用运行时计算出来的字符串和对象作方法名和参数调用对象上的方法

自行添加扩展

GDK为已有Java类添加扩展对于Java程序员来说就是黑魔法,虽然讲清楚黑魔法如何工作比较麻烦,但至少可以讲一下如何添加自定义的黑魔法。
要添加自己的扩展,就需要提供一个jar:

  • 在jar中包含一系列static方法,如果要为对象添加扩展,第一个参数要是对象类型;
  • METAINF/services目录下的org.codehaus.groovy.runtime.ExtensionModule文件中需要声明四个kv对:
    • moduleName=<模块名>
    • moduleVersion=<版本标识>
    • extensionClasses=<包含实例方法定义的类名>
    • staticExtensionClasses=<包含静态方法定义的类名>

使用Groovy脚本

要在Groovy中调用另一个Groovy脚本,可以使用GroovyShell类,调用时可以传入一个Binding对象,为被调用的脚本设置全局binding变量;
也可以在Java中调用Groovy脚本,相关标准为JSR223,使用Java调用Groovy脚本时同样可以传入一个Map作为Groovy脚本的binding;

编译时元编程(AST)

编译时修改AST

可以编写自己的AST处理逻辑,参与到脚本的编译过程中,一个包含AST处理逻辑的jar包需要在manifest/METAINF/services/org.codehaus.groovy.transform.ASTTransformation文件中声明AST类,然后只要在调用groovyc进行编译时,classpath包含这样的jar包,这些jar包的AST处理逻辑就会被自动地应用。
利用自定义AST能力,不仅可以实现自定义的编译检查逻辑,还可以动态修改AST结构。gradle中的<<就是在编译时被替换成了一个方法调用,虽然个人认为gradle这样做其实仅仅是让初学者更难以理解gradle的语法,但只要合理利用,可以使用自定义AST做到很多极具想象力的事情。
可以使用@GroovyASTTransformationClass注解来标记一个注解A,这样使用A注解标记的类就会在编译时执行指定的Transformer的AST变化操作了。
可以使用Groovy的原始AST接口来构造AST,但groovy其实提供了更好用的ASTBuilder来简化AST创建操作。在创建自己的AST处理器时,记得check一下ASTBuilder#buildFromCode方法。