4

博客主页

上一章已经讲解完字符串、集合、运算符重载等,接下来讲解闭包、流程控制、方法、类等核心基础。

闭包

闭包是一种表示可以执行代码块的方法,且闭包也是对象,可以像方法一样传递参数。在声明闭包后,可以使用并修改其作用域内的所有变量值。

闭包语法定义

参数列表 -> 实现体

不带参数的闭包,可以省略参数->,调用直接调用call方法

// 闭包及其调用方法
def closure = { println "Hello Groovy" }
closure.call() // Hello Groovy

闭包可以像方法一样,在闭包中声明中引入形参;

// 参数化的闭包
def closure = { name -> println "Hello ${name}" }
closure.call("Groovy") // Hello Groovy
// 省略了call
closure("Gradle") // Hello Gradle

在闭包中,如果只有一个形参时,可以省略形参名,直接只用隐参数it;当有多个参数时,it就不能表示了。

// 单个隐参数
def closure = { println "Hello ${it}" }
closure.call("Groovy") // Hello Groovy
closure("Gradle") // Hello Gradle

 // 多个参数
 eachMap { k, v ->
     println "${k}, ${v}"
 }
/**
 * 自定义遍历Map的函数,参数接受一个闭包
 */
def eachMap(closure) {
    def map = ['name': 'kerwin', 'age': 12]
    map.each {
        closure.call(it.key, it.value)
    }
}

闭包可以访问属性值

// 闭包和作用范围
def string = "Hello"
def closure = { name -> println "${string} ${name}" }
closure.call("Groovy") // Hello Groovy

// 修改属性值
string = "Welcome"
closure.call("Groovy") // Welcome Groovy

闭包的作用域

def string = "Hello"
def closure = { name -> println "${string} ${name}" }
closure.call("Groovy") // Hello Groovy

string = "Welcome"
closure.call("Groovy") // Welcome Groovy

def test(clos) {
    def string = "My" // 不生效的代码
    clos.call("book")
}

test(closure) // Welcome book

/**
 * 闭包在实参列表外部
 */
// 调用闭包对象的引用
test closure // Welcome book
// 调用闭字面值
test {
    name -> println "Welcome ${name}"
} // Welcome book

upto方法在每次调用闭包时,其迭代都从它接受的数字值开始,止于给定的参数值。downto方法在每次调用闭包时,其迭代都从他给定的参数值开始,止于他接受的数字值。下面使用闭包求阶乘示例

println fac(10) // 55

/**
 * 求number的阶乘, upto
 */
def fac(int number) {
    def result = 0
    1.upto(number) {
        num -> result += num
    }
    result
}


println fac2(10) // 55
/**
 * 求number的阶乘, downto
 */
def fac2(int number) {
    def result = 0
    number.downto(1) {
        num -> result += num
    }
    result
}

闭包、集合和字符串

闭包常常用于列表、映射和字符串结合使用。例如遍历每一个元素

[11, 12, 13, 14].each {
    print "${it} "
} // 11 12 13 14

["Java": 121, "Android": 122, "Groovy": 123].each {
    println it
}
//Java=121
//Android=122
//Groovy=123

["Java": 121, "Android": 122, "Groovy": 123].each {
    println "key is ${it.key}, value is ${it.value}"
}
//key is Java, value is 121
//key is Android, value is 122
//key is Groovy, value is 123

闭包的特性

由于闭包也是一个对象,所以可以作为方法的参数。

def list = [11, 12, 13, 14]

// 偶数
def isEven = { num -> return num % 2 == 0 }
// 奇数
def isOdd = { num -> return !isEven.call(num) }

// 根据传入的闭包过滤满足条件的元素
def filter(list, clos) {
    return list.findAll(clos)
}

def evens = filter(list, isEven)
println "evens is ${evens}" // evens is [12, 14]

def odds = filter(list, isOdd)
println "odds is ${odds}" // odds is [11, 13]

闭包也可以作为另一个闭包的参数

def table = [11, 12, 13, 14, 15]

// 奇数
def isOdd = { num -> return num % 2 != 0 }
// 偶数
def isEven = { num -> return !isOdd.call(num) }

def filter = { clos, list ->
    def result = []
    for (element in list) {
        if (clos.call(element)) {
            // << 运算符追加新元素
            result << element
        } else {
            result
        }
    }
    return result
}

def odds = filter.call(isOdd, table)
println "odds id ${odds}" // odds id [11, 13, 15]

def evens = filter.call(isEven, table)
println "evens is ${evens}" // evens is [12, 14]

闭包作为返回值

// 返回闭包
def multiply(x) {
    return { y -> x * y }
}

def result = multiply(2)
println "${result.call(3)}" // 6

闭包关键变量

Groovy闭包的强大之处在于它支持闭包方法的委托。Groovy的闭包有this、owner、delegate这三个属性,当在闭包内调用方法时,由它们来确定使用哪个对象来处理。

默认情况下delegateowner是相等的,但是delegate是可以修改的,Gradle中的闭包很多功能都是通过修改delegate实现的。

下面讨论this、owner、delegate闭包三个重要变量:

this代表闭包定义处的类
owner代表闭包定义处的类或者对象
delegate代表任意对象,默认与owner一致

首先test.groovy脚本文件中定一个闭包,然后打印这三个关键字,发现打印的信息一样。

def scriptClouser = {
    println "scriptClouser this: " + this
    println "scriptClouser owner: " + owner
    println "scriptClouser delegate: " + delegate
}
scriptClouser.call()

//scriptClouser this: com.kerwin.groovy.object.test@740cae06
//scriptClouser owner: com.kerwin.groovy.object.test@740cae06
//scriptClouser delegate: com.kerwin.groovy.object.test@740cae06

然后在test.groovy脚本文件中定义一个内部类Person,然后打印这三个关键字,static代表当前类,打印的信息也都一样。

class Person{
    def static classClouser = {
        println "classClouser this: " + this
        println "classClouser owner: " + owner
        println "classClouser delegate: " + delegate
    }
//    classClouser this: class com.kerwin.groovy.object.Person
//    classClouser owner: class com.kerwin.groovy.object.Person
//    classClouser delegate: class com.kerwin.groovy.object.Person

    def static speech() {
        def methodClassClouser = {
            println "methodClassClouser this: " + this
            println "methodClassClouser owner: " + owner
            println "methodClassClouser delegate: " + delegate
        }
//        methodClassClouser this: class com.kerwin.groovy.object.Person
//        methodClassClouser owner: class com.kerwin.groovy.object.Person
//        methodClassClouser delegate: class com.kerwin.groovy.object.Person
        methodClassClouser.call()
    }
}

Person.classClouser.call()
Person.speech()

在test.groovy脚本文件中定义一个闭包,然后在闭包中定一个闭包,然后打印这三个关键字,发现打印信息不同。其实this就是这个构建脚本的上下文

def outerClouser = {
    def innerClouser = {
        println "innerClouser this: " + this
        println "innerClouser owner: " + owner
        println "innerClouser delegate: " + delegate
    }
    innerClouser.call()
}
//innerClouser this: com.kerwin.groovy.object.test@2a265ea9
//innerClouser owner: com.kerwin.groovy.object.test$_run_closure2@793f29ff
//innerClouser delegate: com.kerwin.groovy.object.test$_run_closure2@793f29ff
outerClouser.call()

// 如果修改innerClouser 闭包的delegate,打印信息
innerClouser.delegate = new Person()
//innerClouser this: com.kerwin.groovy.object.test@2a265ea9
//innerClouser owner: com.kerwin.groovy.object.test$_run_closure2@793f29ff
//innerClouser delegate: com.kerwin.groovy.object.Person@3e8c3cb

闭包委托策略

定义一个Student类

class Student {
    def name
    def printName = { "My name is ${name}" }

    @Override
    String toString() {
        return printName.call()
    }
}

定义Teacher类

class Teacher {
    def name
}

设置student的printName闭包delegate为teacher, 闭包策略为DELEGATE_FIRST
DELEGATE_FIRST:首先代理类找,找不到从自己类找,自己类中找不到,抛出groovy.lang.MissingXXXException异常
DELEGATE_ONLY:只会在代理类找, 找不到抛出groovy.lang.MissingXXXException异常

def student = new Student(name: 'kerwin')
def teacher = new Teacher(name: 'CV')
student.printName.delegate = teacher
// 委托模式优先
student.printName.resolveStrategy = Closure.DELEGATE_FIRST
println student.toString() // My name is CV

在Gradle中,一般会指定delegate为当前的it,在闭包内就可以对该it进行配置,或者调用其方法

 person {
     println "this: ${this}"  // this: com.kerwin.groovy.file.test@5a45133e
     println "owner: ${owner}" // owner: com.kerwin.groovy.file.test@5a45133e
     println "delegate: ${delegate}" // delegate: com.kerwin.groovy.Person@771a660
 
     name = 'kerwin'
     age = 23
     printString() // printString: name is kerwin, age is 23
 }


def person(Closure<Person> closure) {
    def p = new Person()
    println "${p}" // com.kerwin.groovy.Person@771a660
    closure.delegate = p
    // 委托模式优先
    closure.resolveStrategy = Closure.DELEGATE_FIRST
    closure.call(p)
}

class Person implements Serializable {
    def name
    def age

    def printString() {
        println "printString: name is ${name}, age is ${age}"
    }
}

上例中,设置闭包的委托对象为当前创建的Person对象,并设置委托模式优先。在使用person方法创建一个Person的示例时,可以在闭包里直接对该Person实例配置。在Gradle中有很多类似的用法,基本上都是使用delegate的方式使用闭包进行配置。

流程控制语句

while语句、for语句、if语句、switch语句。使用方式跟Java一样,下面主要讲解与Groovy不同之处。
for语句

// 范围
for (count in 1..3) {
    println "count: ${count}"
}

// List集合
for (count in [1, 2, 3]) {
    println "count: ${count}"
}

//Map集合
for (entry in ["ker": 21, "john": 22]) {
    println "key is ${entry.key}, value is ${entry.value}"
}

// 字符串
def result = []
for (letter in 'Groovy') {
    result << letter
}
println result.toListString() // [G, r, o, o, v, y]

switch语句

def number = 1234
switch (number) {
    case 111..115:
        println "范围"
        break
    case [11, 12, 13, 14]:
        println "集合"
        break
    case ~"[0-9]{4}":
        println "正则表达式"
        break
}
// 正则表达式

方法

在这里我主要讲解Groovy方法与Java的不同,然后能够看懂Gradle脚本里的代码就可以了。

括号可以省略不写

estMethod(1, 2)
// 括号可以省略不写
testMethod 1, 2

def testMethod(a, b) {
    println "a + b = ${a + b}"
}

return可以不写的

Groovy中,定义有返回值的方法时,return语句是可以不写的。当不写return时,Groovy 会把方法执行过程中的最后一行代码作为返回值。

def result = testMethod 2, 3
println result

def testMethod(a, b) {
    "a + b = ${a + b}"
}

闭包是可以作为参数传递的

 // 比较呆板的写法
 def list = [11, 12, 13, 14]
 list.each({
     println it
 })
 
 // Groovy规定,如果方法的最后一个参数是闭包,可以放到方法外面
 list.each() {
     println it
}

// 然后方法可以省略,就变成我们经常看到的样式
list.each {
    println it
}

方法的默认参数

方法通常需要用关键字def声明。默认参数仅能出现在非默认参数之后。

def outInfo(params1, parames2 = 'Groovy') {
    println "params1: ${params1}, parames2: ${parames2}"
}

outInfo("Hello", "Android") //params1: Hello, parames2: Android
// 如果没有传入第二个参数,就会使用默认值
outInfo("Hello")  //params1: Hello, parames2: Groovy

Groovy中调用类方法逻辑,如下图展示:

类是创建对象实例的模板。首先定义一个简单描述银行的类,并添加两个属性。接下来创建这个类的对象和显示其属性值的代码。

class Account {
    def id  // 账号
    def amount  // 账户余额
}

//创建对象,使用命名参数模式
def account = new Account(id: "ABC123", amount: 349)
println "Account ${account.id} has amount ${account.amount}"
// Account ABC123 has amount 349
// 上面创建对象的方式等价于下面三行代码
// def account = new Account()
// account.setId("ABC123")
// account.setAmount(349)

def amount = account.getAmount()
println "${amount}"

上面示例隐藏大量信息。首先类Account中的两个属性拥有公共访问权限,其次account.amount所示的属性引用方法实际上是通过account.getAmount()实现的。也就是说,方法getter和方法setter都是Groovy类的隐含方法。

在类中可以定义方法,我们现在为Account类中添加存款和取款的方法。

class Account {
    def id  // 账号
    def amount  // 账户余额
    // 存款
    def add(number) {
        amount += number
    }
    // 取款
    def remove(number) {
        amount -= number
        return number
    }

    @Override
    String toString() {
        return "${id}:${amount}"
    }
}

def account = new Account(id: "ABC123", amount: 10)
println account.toString() // ABC123:10

account.add(100) // 存100
println account.toString() // ABC123:110

account.remove(30) // 取出30
println account.toString() // ABC123:80

在java中,Account类需要有初始化对象的构造器方法,在Groovy中并不需要,只需new操作符和指定参数即可。但是Groovy也有构造器。

实例:设计银行系统建模。一个银行对应多个账户,使用映射处理一对多的关系。其中账户号作为key,Account作为值

class Bank {
    // 银行名称
    def name
    // key : 账号 , value : 账户Account
    def accounts = [:]

    /**
     * 打开或者创建账户
     */
    def openOrCreateAccount(id, amount) {
        def account = this.findAccount(id)
        println account
        if (account != null) {
            account.add(amount)
            return
        }

        account = new Account(id: id, amount: amount)
        accounts[id] = account
    }

    /**
     * 删除账户
     */
    def deleteAccount(id) {
        def account = findAccount(id)
        if (account != null) accounts.remove(id)
    }

    /**
     * 查找账户
     */
    def findAccount(id) {
        if (accounts.isEmpty()) return null

        def acc = accounts.find {
            entry -> return entry.key == id
        }
        return acc != null ? acc.value : null
    }

    /**
     * 获取总资产
     */
    def getTotalAssets() {
        if (accounts.isEmpty()) return 0

        def total = 0
        accounts.each { id, account -> total += account.amount}
        return total
    }
}

元编程

接下来主要 介绍Groovy的元对象协议MOP(Mate-Object-Protocol)的组成部分,MOP是在运行时改变对象和类行为的系统的能力。

元类(MetaClass)的概念

在Groovy中,所有对象都实现了GroovyObject接口,声明在groovy.lang包中

 public interface GroovyObject {
 
     Object invokeMethod(String name, Object args);
 
     Object getProperty(String propertyName);
 
     void setProperty(String propertyName, Object newValue);
 
     MetaClass getMetaClass();

     void setMetaClass(MetaClass metaClass);
}

GroovyObject 与 MetaClass 协作,MetaClass 是Groovy元概念的核心,它提供了一个Groovy类的所有的元数据,也实现了接口MetaObjectProtocol:

Object invokeMethod(Object object, String methodName, Object[] arguments);

Object invokeMethod(Object object, String methodName, Object arguments);

Object invokeStaticMethod(Object object, String methodName, Object[] arguments);

Object invokeConstructor(Object[] arguments);

上面这些方法进行真正的方法调用,使用java反射,GroovyObject 的 invokeMethod方法默认实现转到相应的MetaClass 。MetaClass 被存储在MetaClassRegistry中,通过groovy也从MetaClassRegistry中获取MetaClass 。

方法调用和拦截

 // MyMetaTest.groovy
 class MyMetaTest {
     def hello() {
         return 'invoked hello directly'
     }
 
     @Override
     Object invokeMethod(String name, Object args) {
         return "invokeMethod: name:${name}, args:${args}"
    }
}

MyMetaTest test = new MyMetaTest()
println test.hello() // invoked hello directly

println test.hello2('kerwin') // invokeMethod: name:hello2, args:[kerwin]

在Groovy中任何对象都实现GroovyObject接口,所以MyMetaTest 也默认实现了GroovyObject接口。如果调用MyMetaTest 中定义了的方法,如:hello,就会直接调用。如果调用MyMetaTest 中未定义方法,如:hello2,如果覆盖了invokeMethod就会执行invokeMethod方法,否则抛出MissingMethodException异常。

 class MyMetaTest implements GroovyInterceptable{
     def hello() {
         return 'invoked hello directly'
     }
 
     @Override
     Object invokeMethod(String name, Object args) {
         return "invokeMethod: name:${name}, args:${args}"
     }
}

MyMetaTest test = new MyMetaTest()

println test.hello() // invokeMethod: name:hello, args:[]

println test.hello2('kerwin') // invokeMethod: name:hello2, args:[kerwin]

println test.&hello() // invoked hello directly

让MyMetaTest 实现GroovyInterceptable接口,该接口是一个标记接口,没有任何方法需要实现。从这个接口的描述可知:实现该接口的类,类中的方法被调用时都会默认使用invokeMethod方法,不管该方法是否已经定义。如果要直接调用已定义的方法,需要使用.&操作符。

 class MyMetaTest2 {
     def greeting = 'accessed greeting directly'
 
     @Override
     void setProperty(String propertyName, Object newValue) {
         println "setProperty>>>> propertyName:${propertyName}, newValue:${newValue}"
         if ('greeting' == propertyName) {
             greeting = newValue
         }
    }

     @Override
     Object getProperty(String propertyName) {
        println "getProperty>>>> propertyName:${propertyName}"
        if (propertyName == 'greeting') return greeting
        return ""
    }
}

MyMetaTest2 test2 = new MyMetaTest2()

println test2.greeting
//getProperty>>>> propertyName:greeting
//accessed greeting directly

test2.greeting = 'Hello Groovy, My is Kerwin.'
println test2.greeting // Hello Groovy, My is Kerwin.
//setProperty>>>> propertyName:greeting, newValue:Hello Groovy, My is Kerwin.
//getProperty>>>> propertyName:greeting
//Hello Groovy, My is Kerwin.

println test2.@greeting
// Hello Groovy, My is Kerwin.

该示例描述了属性的获取特性,如果想直接访问定义的属性,可以使用.@操作符, 对于类中不存在的属性,不要使用.@操作符,否则会抛出MissingFieldException异常。

metaClass

定义一个Person类

/**
 * groovy中默认都是public
 */
class Person implements Serializable {
    String name
    Integer age

    def increaseAge(Integer years) {
        this.age += years
    }

    /**
     * 一个方法找不到时,调用它代替
     * @param name
     * @param args
     * @return
     */
    def invokeMethod(String name, Object args) {
        return "the method is ${name}, the params is ${args}"
    }

    def methodMissing(String name, Object args) {
        return "the method ${name} is missing"
    }
}

首先定义一个全局管理类ApplicationManager,通过metaClass为Person类注入一个静态方法createPersion

class ApplicationManager {
    def static init() {
       // 开启全局设置
        ExpandoMetaClass.enableGlobally()
        // 注入createPersion 静态方法
        Person.metaClass.static.createPersion = { name, age ->
            new Person(name: name, age: age)
        }
    }
}

调用Person注入的静态createPersion方法

class Entry {
    static void main(def args) {
        ApplicationManager.init()
        Person person = Person.createPersion('kerwin', 123)
        println "name: ${person.name}, age:${person.age}"
        //name: kerwin, age:123
    }
}

如果我的文章对您有帮助,不妨点个赞鼓励一下(^_^)


小兵兵同学
56 声望23 粉丝

Android技术分享平台,每个工作日都有优质技术文章分享。从技术角度,分享生活工作的点滴。