Groovy入门

安装以及简单使用

安装

到Groovy官网下载安装包。(本人使用的是windows,故以windows为例)

  1. 按照安装程序指引操作。
  2. 设置GROOVY_HOME环境变量和Groovy路径。GROOVY_HOME设置为安装目录(C:\Groovy\Groovy-2.4.7),Path中添加%GROOVY_HOME%/bin。
  3. 在命令行中运行groovy -v,验证是否安装正确。

使用groovysh

在命令行窗口输入groovysh,我们会看到一个shell。然后就可以输入一些groovy代码了。

groovy:000> Math.sqrt(16)
===> 4.0
groovy:000>

输入:exit退出shell。

使用groovyConsole

windows用户可以在安装目录的bin中找到groovyConsole.bat,双击,控制台GUI工具就会弹出。可以在GUI上进行编码操作。

命令行中运行Groovy文件

  1. 编写一个groovy文件。
  2. 运行groovy命令执行。

    hello.groovy内容: println "Hello Groovy!"
    
    运行命令: groovy hello
    
    结果:Hello Groovy!
    

Groovy基础

Groovy支持Java语法,并且保留了Java语义,我们可以随心所欲地混用两种语言。

一些特性:

  • return方法几乎总是可选的
  • 可以不使用分号分隔语句
  • 方法和类默认是public的
  • 静态方法内可以使用this来引用Class对象。
  • Groovy的==等价于Java的equals()。如果实现了Comparable接口,那么==就相当于compareTo()方法
  • def、in、it不建议作为变量的命名。def用于定义方法、属性和局部变量;in用于在for循环中指定循环期间。
  • Groovy闭包用花括号定义,匿名类也用花括号定义

http://groovy-lang.org/differences.html列出来Groovy和Java的不同。

实现循环的方式

for循环 & range语法

for (int i = 0; i < 3; i++){
    println("this is " + i);
}


for(i in 0..2){
    println("this is " + i);
}

upto()

0.upto(2){
    println("this is $it");
}

0是Integer的一个实例。$it代表进行循环时的索引值。upto()方法接受一个闭包作为参数。

times()

3.times {
    println("this is $it");
}

范围从0开始。

step()

0.step(10,2){
    println("this is $it");
}

表示,从0到10,每次加2。

安全导航操作符 - ?.

安全导航(safe-navigation)操作符(?.)在安全的情况下才会去调用指定的方法或者属性。

def foo(str){
//    if(str !=null ) str.reverse();
    str?.reverse();
}

println(foo('evil'))
println(foo(null))

异常处理

对于那些我们不想处理的异常,或者不适合在代码的当前处理的异常,Groovy并不强制我们处理。我们不处理的任何异常都会被自动传递给更高一层。所以,我们可以在更高层次的任何地方处理我们想要处理的异常。

JavaBean

class Car {

    def miles = 0;
    final year;

    Car(theYear){
        year = theYear;
    }
}


Car car = new Car(2016);

println "Year: $car.year";
println "Miles: $car.miles";
println "Setting miles: ";
car.miles = 25;
println "Miles: $car.miles";
  • def声明一个属性。
  • Groovy会为JavaBean创建一个访问器(getter)和一个更改器(setter)。
  • 使用final来声明属性时,该属性为只读。修改final字段会导致异常。
  • Groovy的实现不区分public、private和protected。
  • 要把变量设置为私有的,必须实现一个拒绝任何修改的更改器(setter)。

示例如下:

class Car {

    private miles = 0;
    final year;

    Car(theYear){
        year = theYear;
    }

    def getMiles(){
        miles;
    }

    private void setMiles(miles){
        throw new IllegalAccessException("you're not allowed to change miles") ;
    }

}

初始化和具名参数

class Robot{
    def type,height,width
    def access(location,weight,fragile){
        println "Received fragile? $fragile, weight: $weight, loc: $location"
    }
}

robot = new Robot(type:'arm',width: 0,height: 40)
println "$robot.type, $robot.height, $robot.width"

robot.access(x:30,y:20,z:10,50,true)
robot.access(50,true,x:30,y:20,z:10)
robot.access(50,x:30,y:20,z:10,true)
  • 在构造对象时,可以简单得以逗号分隔的“名值对”来给出“属性值”。第一个形参必须定义为Map。要让这种操作正确执行,必须要有一个无参构造器(编译器会默认提供一个)。此操作会在无参构造方法之后执行。
  • 在使用方法时,如果第一个是Map,则可以将这个映射中的键值对展开放在实参的列表中。如果实参的个数多于方法的形参的个数,而且多出来的实参是键值对,那么Groovy会假设方法的第一个形参是Map,然后将实参列表中的素有键值对组织到一起,作为第一个形参的值。之后,再将剩下的实参按照给出的顺序赋给其余的形参。

站在阅读代码的角度,真的很不建议上述的做法。我们可以明确将一个形参指定为Map。

def access(Map location,weight,fragile){
    //TODO
}

可选形参

Groovy可以把构造方法和方法的形参设置为可选的。要定义可选形参,只需要在形参列表中给它赋一个值就可以了。Groovy还把最后一个为数组的形参视为可选的。

def log(x,base = 10){
    Math.log(x) / Math.log(base)
}
println log(1024)
println log(1024,2)


def task(name,String[] details){
    println "$name - $details"
}
task('Hing','150-0000-0000')
task('Hing','150-0000-0000','158-0000-0000')
task('Hing')

使用多赋值

  • 要从方法返回多个结果,并将它们一次性赋给多个变量,我们可以返回一个数组,然后将多个变量以逗号分隔,放在圆括号中,置于赋值表达式左侧即可
  • 可以用此特性来交互变量,无需创建中间变量来保存被交换的值,只需将与交换的变量放在圆括号内,置于赋值表达式左侧,同时将它们以相反顺序放在方括号内,置于右侧即可
  • 如果有多余的变量,Groovy会将它们设置为null;多余的值则会被丢弃。

示例:

//返回多个结果
def splitName(fullName){
    fullName.split(' ')
}
def (fistName,lastName) = splitName('Hing Kwan')
println "$fistName, $lastName"


//交换变量
def name1 = "Thomson"
def name2 = "Thompson"
println "$name1 and $name2"
(name1,name2) = [name2,name1]
println "$name1 and $name2"

//多的参数
def(String cat,String mouse) = ['Tom','Jerry','Spike','Tyke']
println "$cat and $mouse"

//多的结果
def(first,second,third) = ['Tom','Jerry']
println "$first and $second and $third"

实现接口

在Groovy中,可以把一个映射或一个代码块转化为接口,因此可以快速实现带有多个方法的接口。

Groovy没有强制实现接口中的所有方法:可以只定义自己关心的,而不考虑其他方法。如果剩下的方法从来不会被调用,那也就没有必要去实现这些方法了。

接口中的每个方法可能需要不同的实现,在Groovy中只需要创建一个映射,以每个方法的名字作为键,以对应的代码体作为值,同时使用简单的Groovy风格,用冒号(:)分隔方法名和代码块即可。

如果没有提供实现的方法被调用了,则会抛出NullPointerException。

布尔求值

根据上下文,Groovy会自动把表达式计算为布尔值。

以下是为true的条件

  • Boolean:值为true
  • Collection:集合不为空
  • Character:值不为0
  • CharSequence:长度大于0
  • Enumeration:hasMoreElements()为true
  • Iterator:hasNext() 为true
  • Number:Double值不为0
  • Map:该映射不为空
  • Matcher:至少有一个匹配
  • Object[]:长度大于0
  • 其他任何类型:引用不为null

操作符重载

Groovy支持操作符重载。因为在Groovy中,每个操作符都会映射到一个标准的方法。

for(ch = 'a';ch < 'd';ch++){
    println(ch)
}
for(ch in 'a'..'c'){
    println(ch)
}

enum

示例一:

enum CoffeeSize {
    SHORT, SMALL, MEDIUM, LARGE, MUG
}

def orderCoffee(size){

    print "Coffee order received for size $size :"

    switch (size){
        case [CoffeeSize.SHORT,CoffeeSize.SMALL]:
            println "you're health conscious"
            break;
        case CoffeeSize.MEDIUM..CoffeeSize.LARGE:
            println "you gotta be a programmer"
            break;
        case CoffeeSize.MUG:
            println "you should try Caffeine IV"
            break;
    }
}

orderCoffee(CoffeeSize.SMALL)
orderCoffee(CoffeeSize.LARGE)
orderCoffee(CoffeeSize.MUG)

print 'Available sizes are:'
for(size in CoffeeSize.values()){
    print "$size";
}

示例二:

enum Methodologies{
    Evo(5),
    XP(21),
    Scrum(30);

    final int daysInIteration
    Methodologies(days){
        daysInIteration = days
    }

    def iterationDetails(){
        println "${this} recommends $daysInIteration days for iteration"
    }
}

for(methodology in Methodologies.values()){
    methodology.iterationDetails()
}

变长参数

Groovy以两周方式支持变长参数。1)支持使用省略号标记形参;2)以数组作为末尾形参的方法。

def receiveVarArgs(int a,int ... b){

}

def receiveArray(int a,int[] b){

}

静态导入

import static Math.random as rand
import groovy.lang.ExpandoMetaClass as EMC

double val = rand();
def metaClass = new EMC(Integer);

一些特色的注解

@Canonical

可以条件地使用或者去掉字段。

import groovy.transform.Canonical

@Canonical(excludes = "lastName,age")
class Person{
    String firstName;
    String lastName;
    int age;
}

def sara = new Person(firstName: "Sara",lastName: "Walker",age: 49)
println sara

@Delegate

使用@Delegate可以很方便地使用委托。

class Worker{
    def work(){println 'get work done'}
    def analyze(){println 'analyze...'}
    def writeReport(){println 'get report written'}
}

class Expert{
    def analyze(){println 'expert analysis...'}
}

class Manager{
    @Delegate Expert expert = new Expert();
    @Delegate Worker worker = new Worker();
}

def bernie = new Manager()
bernie.analyze()
bernie.work()
bernie.writeReport()

上面代码中因为Expert先被引入,所以analyze方法也先被引入,后面的就不引入相同的方法。

@Immutable

用@Immutable注解标记一个类,Groovy会将其字段标记为final,并且额外为我们创建一些便捷的方法。

使用@Immutable注解可以轻松创建轻量级不可变对象。不可变实例是作为消息传递的理想实例。

@Immutable
class CreditCard{
    String cardNumber;
    int creditLimit;
}

println new CreditCard("",1000)

@Lazy

使用@Lazy可以把耗时对象的构建推迟到真正需要时。可以自动将字段标记为volatile,并确保创建期间是线程安全的。

class Heavy{
    def size = 10;
    Heavy(){println "Creating Heavy with $size"}
}

class AsNeeded{
    def value

    @Lazy Heavy heavy1 = new Heavy()
    @Lazy Heavy heavy2 = {new Heavy(size: value)}()

    AsNeeded(){println "Created AsNeeded"}
}

def asNeeded = new AsNeeded(value: 100)
println asNeeded.heavy1.size
println asNeeded.heavy1.size
println asNeeded.heavy2.size

@Newify

可以用来创建实例。在创建DSL时,@Newify注解非常有用,它可以使得实例创建更像一个隐式操作。

@Newify([Person,CreditCard])
def fluentCreate(){
    println Person.new(firstName: "John",lastName: "Doe",age: 20)
    println Person(firstName: "John",lastName: "Doe",age: 20)
    println CreditCard("",1000)
}

fluentCreate();

@Singleton

@Singleton(lazy = true,strict=false)
class TheUnique{

    TheUnique(){
        println 'TheUnique created'
    }

    def hello(){
        println 'hello'
    }
}
TheUnique.instance.hello()
TheUnique.instance.hello()

鸭子类型

def takeHelp(helper){
    helper.helpMoveThings();
}

class Man{
    void helpMoveThings(){
        println "Man's helping"
    }
}

class Woman{
    void helpMoveThings(){
        println "Woman's helping"
    }
}

class Elephant{
    void helpMoveThings(){
        println "Elephant's helping"
    }
}


takeHelp(new Man())
takeHelp(new Woman())
takeHelp(new Elephant())

多方法

如果一个类中有重载的方法,Groovy会聪明地选择正确的实现——不仅基于目标对象(调用方法的对象),还基于所提供的参数。因为方法分派基于多个实体——目标加参数,所以这被称为多分派或多方法。

ArrayList<String> lst = new ArrayList<>();
Collection<String> col = lst;

lst.add("One");
lst.add("Two");
lst.add("Three");
col.remove(0);
col.remove(0);

println lst;
println col;

静态类型检查

可以使用@TypeChecked注解进行静态类型检查。这个注解可以用于类或者单个方法上。如果用于一个类,则类型检查会在该类中所有方法、闭包和内部类上执行。如果用于一个方法,则类型检查仅在目标方法的成员上执行。

要利用静态类型检查,必须要指明方法和闭包的形参类型。

闭包

闭包基础

先从传统的方式看起,也许在我们编码的过程中,会有以下代码:

def sum(n){
    total = 0
    for(int i = 2; i <= n ;i+=2){
        total += i
    }
    total
}

def product(n){
    prod = 1
    for(int i = 2 ; i <= n; i+=2){
        prod *= i
    }
    total
}

sum(10);
product(10);

用闭包实现为:

def pickEven(n,block){
    for(int i = 2; i <= n; i++){
        block(i);
    }
}
pickEven(10,{println it});

就像上面的调用一样,代码块({}内的代码)被传给形参block,也就是block代表了传入的代码块执行。

如果闭包是最后一个实参,调用可以用下面的优雅的写法:

pickEven(10){println it}

所以,当闭包是方法调用的最后一个实参时,可以将闭包附到方法调用上。这种情况下,代码块看上去就像是附在方法调用上的寄生虫。Groovy的闭包不能单独存在,只能附到一个方法上,或是赋值给一个变量。

上面代码中的it代表的意思:如果只向代码中传递一个参数,那么可以使用it这个特殊的变量名来指代该参数。我们也可以使用其他名称来代替it,如:

pickEven(10){evenNum -> println evenNum}

闭包有两个非常擅长的领域:1)辅助资源清理;2)辅助创建内部的领域特定语言

普通函数在实现某个目标明确的任务时优于闭包。重构的过程是引入闭包的好时机。

闭包的使用

将闭包赋值给变量并复用

def totalSelectValues(n,closure){
    total = 0
    for(i in 1..n){
        if(closure(i)){
            total += i
        }
    }
    total
}

println totalSelectValues(10){it % 2==0}

def isOdd = {it % 2 != 0}
println totalSelectValues(10,isOdd)



class Equipment{
    def calculator
    Equipment(calc){
        calculator = calc
    }

    def simulate(){
        println "Running simulation"
        calculator()
    }
}

def eq1 = new Equipment({ println "Calculator 1"})
def aCalculator = { println "Calculator 2"}
def eq2 = new Equipment(aCalculator)
def eq3 = new Equipment(aCalculator)

eq1.simulate();
eq2.simulate();
eq3.simulate();

向闭包传递多个参数

def tellFortune(closure){
    closure new Date("07/29/2016"),"Your day is filled with ceremony"
}
tellFortune(){
    date,fortune -> println "Fortune for ${date} is '${fortune}'"
}

//以上的代码其实也就是下面的代码:

def tellFortune1(closure){
    closure(new Date("07/29/2016"),"Your day is filled with ceremony")
}
tellFortune1({date,fortune -> println "Fortune for ${date} is '${fortune}'"})

符号->将闭包的参数声明与闭包主体分隔开来。

使用闭包进行资源清理

def writer = new FileWriter("out.txt");
writer.write('..');
// 没有调用writer.close()

//当从闭包返回时,withWriter()会自动刷新并关闭这个流
new FileWriter('out.txt').withWriter {
    writer -> writer.write('!')
}

Execute Around Method模式

编写一个Execute Around方法,它接受一个块作为参数。在这个方法中,把对该块的调用夹到对那对方法的调用之间。即先调用第一个方法,然后调用该块,最后调用第二个方法。方法的使用者不必担心这对动作,他们会自动被调用。我们甚至可以在Execute Around方法内处理异常。

class Resource {
    def open() { println "opened..." }
    def close() { println "closed..." }
    def read() { println "read..." }
    def write() { println "writed..." }
    def static use(closure) {
        def r = new Resource();
        try {
            r.open()
            closure(r)
        } finally {
            r.close()
        }
    }
}
Resource.use {
    res ->
        res.read()
        res.write()
}

闭包与协程

def iterate(n,closure){
    1.upto(n){
        println "In iterate with value ${it}"
        closure(it)
    }
}
println "Calling iterate"
total = 0
iterate(4){
    total += it
    println "In closure total so far is ${total}"
}
println "Done"

上面的代码中,每次调用闭包,我们会从上一次的调用中恢复total值。

科里化闭包

带有预绑定形参的闭包叫科里化闭包。

当对一个闭包调用curry()时,就是要求先绑定某些参数。在预先绑定了一个形参之后,调用闭包时就不必重复为这个形参提供实参了。

可以使用curry()方法科里化任意多个形参,但这些形参必须是从前面开始的连续若干个。也就是说,如果有n个形参,我们可以任意科里化前k个,其中 0 <= k <= n。

如果想科里化后面的形参,可以使用rcurry()方法。如果想科里化位于形参列表中间的形参,可以使用ncurry()方法,传入要进行科里化的形参的位置,同时提供相应的值。

def tellFortunesCurry(closure){
    Date date = new Date();

    postForturne = closure.curry(date)
    postForturne "Your day is filled with ceremony"
    postForturne "They're features,not bugs"
}

tellFortunesCurry(){
    date,fortune ->
        println "Fortune for ${date} is '${fortune}'"
}

动态闭包

在给闭包传递参数时,也有很多灵活性。可以动态地确定一个闭包期望的参数的数目和类型。

即便一个闭包没有使用任何参数,就像{}或{it}中这样,其实它也会接受一个参数(名字默认为it)。如果调用者没有向闭包提供任何值,则第一个形参(it)为null。如果希望闭包完全不接受任何参数,必须使用{->}这种语法:在->之前没有形参,说明该闭包不接受任何参数。

利用maximumNumberOfParameters和parameterTypes属性,我们可以动态地检查闭包,而且在实现某些逻辑时有了更大灵活性。

def completeOrder(amount,taxComputer){
    tax = 0
    if(taxComputer.maximumNumberOfParameters == 2){
        tax = taxComputer(amount,6.05)
    }else{
        tax = taxComputer(amount)
    }
    println "Sales tax is ${tax}"
}

completeOrder(100){it * 0.0825}
completeOrder(100){amount,rate -> amount * (rate/100)}


def examine(closure) {
    println "$closure.maximumNumberOfParameters parameter(s) given:"
    for (aParameter in closure.parameterTypes) {
        println aParameter.name
    }
    println "--"
}

examine() {}
examine() { it }
examine() { -> }
examine() { val1 -> }
examine() { Date val1 -> }
examine() { Date val1, val2 -> }
examine() { Date val1, String val2 -> }

闭包委托

this、owner和delegate是闭包的三个属性,用于确定由哪个对象处理该闭包内的方法调用。一般而言,delegate会设置为owner,但是对其加以修改。

def examiningClosure(closure){
    closure()
}

examiningClosure(){
    println "In First Closure"
    println "class is "+ getClass().name
    println "this is"+this +", super is" + this.getClass().superclass.name
    println "owner is "+ owner+", super is "+owner.getClass().superclass.name
    println "delegate is "+delegate+ ", super is "+delegate.getClass().superclass.name


    examiningClosure(){
        println "In First Closure"
        println "class is "+ getClass().name
        println "this is"+this +", super is" + this.getClass().superclass.name
        println "owner is "+ owner+", super is "+owner.getClass().superclass.name
        println "delegate is "+delegate+ ", super is "+delegate.getClass().superclass.name
    }

}

闭包内的this指向的是该闭包所绑定的对象(正在执行的上下文)。在闭包内引用的变量和方法都会绑定到this,它负责处理任何方法调用,以及对任何属性或变量的访问。如果this无法处理,则转向owner,最后是delegate。

使用尾递归

def factorial(BigInteger number){
    if(number == 1){
        1
    }else{
        number * factorial(number - 1)
    }
}

try {
    println "factorial of 5 is ${factorial(5)} "
    println "factorial of 5000 is ${factorial(5000)}"
}catch (Exception e){
    println ${e.getClass().name}
}

以上代码在5000的时候会报错,那么有什么解决方法呢?

Groovy语言通过闭包上的一个特殊的trampoline()方法提供了解决方法。

def factorial

factorial = { int number,BigInteger theFactorial ->
    number == 1 ? theFactorial : factorial.trampoline(number - 1, number * theFactorial)
}.trampoline()

println "factorial of 5 is ${factorial(5,1)}"
println "factorial of 5000 is ${factorial(5000,1)}"

上面代码中,factorial变量本身被赋值的就是在闭包上调用trampoline()方法的结果。

这种递归叫尾递归,因为方法中最后的表达式是结束递归或者是调用自身。

上面的代码可以优化为:

def factorial(int factorialFor){
    def tailFactorial
    tailFactorial = {
        int number,BigInteger theFactorial = 1 ->
            number == 1 ? theFactorial : tailFactorial.trampoline(number - 1, number * theFactorial)
    }.trampoline()

    tailFactorial(factorialFor)
}

使用记忆缓存改变性能

上面的代码是一种可以使用递归调用更高效地使用内存的技巧。而它的一个变种是将问题分解为可以多次重复解决的若干部分。在执行期间,我们将子问题的结果保存下来,当调用到重复的计算时,只需要简单地使用保存下来的结果。这样就避免了重复运行,因此极大地减少了计算时间。记忆化可以将一些算法的计算时间复杂度从输入规模(n)的指数级(O(k^n))减低到只有线性级(O(n))。

def rodPrices = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30]
def desiredLength = 27

@Immutable
class RevenueDetails {
    int revenue
    ArrayList splits
}

def cutRod(prices, length) {
    if (length == 0) {
        return new RevenueDetails(0, [])
    } else {
        def maxRevenueDetails = new RevenueDetails(Integer.MIN_VALUE, [])
        for (rodSize in 1..length) {
            def revenueFromSecondHalf = cutRod(prices, length - rodSize)
            def potentialRevenue = new RevenueDetails(
                    prices[rodSize] + revenueFromSecondHalf.revenue,
                    revenueFromSecondHalf.splits + rodSize
            )
            if (potentialRevenue.revenue > maxRevenueDetails.revenue) {
                maxRevenueDetails = potentialRevenue
            }
        }
        maxRevenueDetails
    }
}

timeIt desiredLength, { length -> cutRod(rodPrices, length) }

以上代码将花费我们很长时间去计算,优化如下

def cutRod
cutRod ={ prices, length ->
    if (length == 0) {
        return new RevenueDetails(0, [])
    } else {
        def maxRevenueDetails = new RevenueDetails(Integer.MIN_VALUE, [])
        for (rodSize in 1..length) {
            def revenueFromSecondHalf = cutRod(prices, length - rodSize)
            def potentialRevenue = new RevenueDetails(
                    prices[rodSize] + revenueFromSecondHalf.revenue,
                    revenueFromSecondHalf.splits + rodSize
            )
            if (potentialRevenue.revenue > maxRevenueDetails.revenue) {
                maxRevenueDetails = potentialRevenue
            }
        }
        maxRevenueDetails
    }
}.memoize()

在以上代码中,Groovy创建了Memoize类的一个专用实例。该实例中有一个指向所提供闭包的引用,还有一个结果的缓存。当我们调用该闭包时,该实例会在返回结果之前响应缓存下来。在随后的调用中,如果对应某个参数已经有了相应的缓存下来的结果值,则返回该值。

记忆化技术是以空间换取时间。如果大规模计算的话,内存的需求可能会急剧增长。Groovy给我们提供了一些其他的选择,如:采用LRU算法的memoizeAtMost(),可以设置缓存下限的memoizeAtLeast()、可以设置缓存上限以及下限的memoizeBetween()

字符串

在Groovy中,”” 和 ‘’都表示字符串。如果需要显式地创建一个字符,只需要输入 ‘a’ as char。

Groovy的字符串也是不可变的。可以使用[]操作符读取一个字符;不过不能修改。

def printClassIfnfo(obj){
    println "class: ${obj.getClass().name}"
    println "superclass: ${obj.getClass().superclass.name}"
}

val = 25;
printClassIfnfo("The Stock closed at ${val}")
printClassIfnfo(/The Stock closed at ${val}/)
printClassIfnfo("This is a simple String")

看下GString的惰性求值问题:

price = 684.71
company = "Google"
quote = "Today $company stock closed as $price"
println quote

stocks = [Apple:663.01,Microsoft:30.95]
stocks.each{
    key,value ->
        company = key
        price = value
        println quote
}

得到的结果是:

Today Google stock closed as 684.71
Today Google stock closed as 684.71
Today Google stock closed as 684.71

这样的结果明显不是我们想要的。程序修改如下:

companyClosure = { it.write(company) }
priceClosure = { it.write("$price") }
quote = "Today ${companyClosure} stock closed at ${priceClosure}"

stocks = [Apple: 663.01, Microsoft: 30.95]
stocks.each { key, value ->
    company = key
    price = value
    println quote
}

重构改为:
quote = “Today ${->company} stock closed at ${-> price}”
stocks = [Apple: 663.01, Microsoft: 30.95]
stocks.each { key, value ->
company = key
price = value
println quote
}

可以通过将字符串包含在3个单引号内(’’’ … ‘’’)来定义多行字面常量。

langs = ['C++':'Stroustrup','java':'Gosling','Lisp':'McCarthy']
content = ''
langs.each {language,author ->

    fragment = '''
        <language name="${language}">
            <author>${author}</author>
        </language>
    '''

    content += fragment
}
xml = "<languages>${content}</languages>"
println xml

操作符“~”可以方便地创建RegEx模式。这个操作符映射到String类的negate()方法。

要定义一个RegEx,使用正斜杠,如:/[G|g]roovy/

要确定是否存在匹配使用=~

要精确匹配使用==~

集合

lst = [1,3,4,1,8,9,2,6]
println lst
println lst.getClass().name

println lst[0]
println lst[lst.size() - 1]
println lst[-1] //最后一个
println lst[-2]
println lst[2..5]   //从位置2开始,连续4个
println lst[-6..-3]

sublst = lst[2..5]
println sublst.dump()   //打印该实例的详细信息
sublst[0] = 55
println "After sublst[0]=55 lst = $lst"

lst.each { println it}  //集合的迭代

//对每个元素进行操作,并返回集合
println lst.collect({it * 2})

println lst.find( {it == 2})  //查找

println lst.findAll({it == 2})  //查找所有

lst = ['Programming','In','Groovy']
count = 0
lst.each {count += it.size()}
println count

println lst.collect {it.size()}.sum()   //与上面的代码效果一样

//inject对集合中的每个元素进行闭包调用
println lst.inject(0) {carryOver,element -> carryOver+element.size()}

println lst.join(' ')

lst[0] = ['Be','Productive']    //替换
println lst

lst = lst.flatten()
println lst

println lst - ['Productive','In']   //去除元素

println lst.reverse()

println lst.size()
println lst*.size()

Map

langs = ['C++':'Stroustrup','Java':'Gosling','Lisp':'McCarthy']
println  langs.getClass().name
println langs['List']
println langs.Java
println langs.'C++'

langs.each {entry ->
    println "Language $entry.key was authored by $entry.value"
}
langs.each{language,author ->
    println "Language $language was authored by $author"
}

println langs.collect {language,author ->
    language.replaceAll("[+]","P")
}

entry = langs.find{language,author ->
    language.size() > 3
}
println entry.value


selected = langs.findAll {language,author ->
    language.size() > 3
}
selected.each {key,value ->
    println key + ',' +value
}

println  langs.any {language,author ->  //只要有一个符合
    language =~ "[^A-Za-z]"
}

println  langs.every {language,author ->  //每个都符合
    language =~ "[^A-Za-z]"
}

authors = langs.groupBy {it.value.split(' ')[0]}
authors.each {author,values ->
    println "$author : ${values.collect {key,value -> value}.join(', ')}"
}