【Gradle】Groovy的语法详解(下篇)

上文介绍了Groovy的通常省略规则,以及字符串、列表、Map的使用方式,然而在Groovy中,闭包是其中的一大杀器,能够对代码的结构和逻辑带来的优化显而易见。本博文主要对Groovy闭包的使用、文件IO的使用进行讲解。html

本文实例源码github地址https://github.com/yngzMiao/yngzmiao-blogs/tree/master/2020Q1/20200110java


闭包

闭包,是一个代码块,或能够理解成一个匿名函数。在外部方法调用时,能够将其做为方法的实参传递给方法的形参,并在方法内部回调此匿名函数,且回调此匿名函数时能够传递实参给到匿名函数的内部去接收,并执行此匿名函数git

同时,此代码块或匿名函数也能够赋值给一个变量,使其具备自执行的能力,且最后一行的执行语句做为匿名函数的返回。github

闭包基础

可能乍一读并不太可以理解这段话的含义,能够先看几个例子:web

def b1 = {
    println "hello world"
}

b1()             //此处也能够写成b1.call()

运行后,输出结果为:api

hello world

能够看出,所谓闭包,其实就是一段代码块(用大括号包括起来了)。它能够被赋值给一个闭包对象,若是查看此时b1的类型,实际上是Closurebash

既然存在闭包对象,那么很显然,也能够将其做为方法的实参传递给某个方法。例如:闭包

def b1 = {
    println "hello world"
}

def fun(Closure closure) {
  closure()
}

fun(b1)

能够看出,调用fun方法,在fun方法的实现里调用了闭包内容,在闭包内容中进行了打印语句的输出。app

接下来,闭包是能够进行参数传递的,且默认参数为it。固然,也能够显式地定义,经过->来进行定义。例如:svg

def b1 = {
    println "hello ${it}"           //默认参数it
}

def b2 = {
    def v ->                        //指定参数v
       println "hello ${v}"
}

def fun(Closure closure) {
  closure('world')
}

fun(b1)
fun(b2)

也就是说,闭包也是能够传递参数进行闭包内脚本程序地运行的。

到这边,可能你会发现,闭包实现的功能和方法(函数)的功能很像啊。那么闭包对象,实际上也就能够类比是C/C++的函数指针。那这有什么可炫耀的?

再看一个例子:

def b1 = {
    def u, v ->                     //传递两个参数
        println "hello ${u} hello ${v}"
}

def fun(name1, name2, closure) {    //函数省略参数类型
  closure(name1, name2)
}

fun('zhangcan', 'lisi', b1)

运行后,输出结果为:

hello zhangcan hello lisi

根据上面的例子,能够将闭包放在函数的形参位置,整理一下格式:

def fun(name1, name2, closure) {
  closure(name1, name2)
}

fun('zhangcan', 'lisi', {
    def u, v ->
        println "hello ${u} hello ${v}"
})

同时,在Gradle中,若是闭包是方法的最后一个参数,那么闭包能够放在圆括号外面。又能够整理成:

def fun(name1, name2, closure) {
  closure(name1, name2)
}

fun('zhangcan', 'lisi') {
    def u, v ->
        println "hello ${u} hello ${v}"
}

是否是感受,整个结构都变得特别紧凑。若是仍是体会不到闭包带来的好处,在下文介绍Groovy对String、List、Map、fileTree等类型的闭包扩展,你会深入体会到这种方式对代码书写带来的简单和快捷。

闭包最经常使用的状况是:解放循环

闭包与数字

若是须要计算1+2+3+4+5+…+100的结果,可能你会:

def result = 1
for (i in 1..100)
    result += i
println result

但若是使用闭包,能够写:

def result = 1
1.upto(100) {
  result += it
}
println result

实际上,Number类型有一个upto方法,它有两个参数,第一个参数是另外一个Number,第二个参数是一个闭包类型。在方法实现中,会遍历调用upto方法的值和第一个参数的值之间的数,并将其做为参数传递给闭包。那么,咱们只须要在闭包中补充须要对那个数的操做便可。

一样的,除了upto方法以外,还有downto方法实现由大到小的遍历。

闭包与字符串

若是须要把字符串的每一个字符打印出来,使用闭包的话,能够写成:

def str = 'hello world'
str.each {
    println "char: ${it}"
}

实际上,String类型有一个each方法,它仅有一个参数,且此参数为一个闭包类型。在方法实现中,会遍历调用each方法的String对象的全部字符,并将其做为参数传递给闭包。因为闭包参数是仅有的参数,那么将其提出到圆括号外面,并省略方法的括号。

若是须要找出字符串中第一个知足某特定条件的字符(如第一个数字),使用闭包的话,可使用find方法写成:

def str = 'hello 6 world'
println str.find {
    return it.isNumber()
}

须要注意的是,此方法在闭包中须要返回一个布尔类型。当返回值为true的时候,find方法将返回该字符。

闭包与列表

闭包对于列表,一样提供了许多方法。例如:

def l = [6, 5, 8, 3, 7, 1, 4, 2, 0, 9]

println l.sort {                    //排序,由小到大排序
    def u, v ->
        u > v ? 1 : -1
}

println l.find {                    //找出第一个知足条件的列表元素
    return it % 2 == 0
}
println l.findAll {                 //找出全部知足条件的列表元素
    return it % 2 == 0
}

println l.any {                     //列表是否存在知足条件的元素
    return it % 2 == 0
}
println l.every {                   //列表是否都是知足条件的元素
    return it % 2 == 0
}
println l.count {                   //列表中知足条件的元素个数
    return it % 2 == 0
}

println l.max {                     //列表元素进行某运算操做后的最大值
    return Math.abs(it)
}
println l.min {                     //列表元素进行某运算操做后的最小值
    return Math.abs(it)
}

闭包与map

对map进行循环,应该是最多见的一种操做了。闭包对此提供了三种方式:

def colors = ['red': '#FF0000', 'green': '#00FF00', 'blue': '#0000FF']

colors.each {
    println "key: ${it.key} value: ${it.value}"
    println "key: ${it.getKey()} value: ${it.getValue()}"
}

colors.eachWithIndex {
    def u, v->
        println "${v} key: ${u.key} value: ${u.value}"
}

colors.each {
    def u, v->
        println "key: ${u} value: ${v}"
}

总而言之,能够选择each方法、eachWithIndex方法。当选择each方法时,能够选择闭包单参数,此时遍历map传入的参数就是键值对;也能够选择闭包双参数,此时遍历map传入的参数就是key和value。若是选择eachWithIndex方法时,闭包为双参数,第一参数是键值对,第二个参数是该键值对的索引值

闭包除了循环功能以外,还有其余的一些方法:

def colors = ['red': '#FF0000', 'green': '#00FF00', 'blue': '#0000FF']

println colors.find {                   //找出第一个知足条件的元素键值对
    return it.key.equals('green')
}.collect{                              //对键值对进行过滤,返回输出内容
    return it.value
}

println colors.sort {                   //排序,由小到大排序
    def u, v ->
        return u.key > v.key ? 1 : -1
}

println colors.groupBy {                //对键值对进行条件分类
    return it.key.equals('blue') ? 'one' : 'two'
}             //结果:{two={red=#FF0000, green=#00FF00}, one={blue=#0000FF}}

文件IO

Groovy提供了许多用于处理输入/输出的辅助方法。虽然能够在Groovy中使用标准的Java代码来处理这些问题,可是Groovy提供了更方便的方式来处理文件、流、阅读器等。例如,下面这些类中增长的方法:

其实能够看出,这些类中增长的方法,基本都是闭包为参的方法。

读文件

打印文件的全部行,示例代码以下:

def content = new File('test.txt')

println content.getText()               //得到文件内容字符串
println content.readLines()             //将文件每行内容做为元素,造成list列表

若是使用闭包类型为参数的状况下,可使用:

new File('test.txt').eachLine {
    println it
}

new File('test.txt').eachLine {
    def u, v ->
        println "line_no: ${v} line_content: ${u}"
}

eachLine方法的闭包单参数时,表示每行的内容;eachLine方法的闭包双参数时,第一个参数表示每行的内容,第二个参数表示行号。

同时,若是出于某种缘由,在eachLine方法体中抛出了一个异常,该方法将确保资源被正确地关闭。这个规则适用于Groovy添加的全部输入/输出资源方法。也所以,在资源访问时,建议使用Groovy提供的闭包参数的方法。

固然,除了这个方法外,Groovy还提供了以下的方式:

new File('test.txt').withReader {
    char[] buffer = new char[100]
    it.read(buffer)
    println buffer
}

写文件

利用闭包参数来写文件,代码也很简短清晰:

new File('test1.txt').withWriter('utf-8') {
    it.writeLine 'hello world'
    it.append 'hello you'
}

.properties文件

在Groovy的文件IO中,特地提一下.properties文件的打开方式:

Properties prop = new Properties()
prop.load(new FileInputStream(new File('test.properties')))
println prop

文件树

import groovy.io.FileType

new File('floder').eachFile {                 //遍历文件夹内的文件和目录
    println it.name
}

new File('floder').eachFileRecurse {          //递归遍历文件夹内的文件和目录
    println it.name
}

new File('floder').eachFileRecurse(FileType.FILES) {        //递归遍历文件夹内的文件
    println it.name
}

相关阅读