上文介绍了Groovy的通常省略规则,以及字符串、列表、Map的使用方式,然而在Groovy中,闭包是其中的一大杀器,能够对代码的结构和逻辑带来的优化显而易见。本博文主要对Groovy闭包的使用、文件IO的使用进行讲解。html
本文实例源码github地址:https://github.com/yngzMiao/yngzmiao-blogs/tree/master/2020Q1/20200110。java
闭包,是一个代码块,或能够理解成一个匿名函数。在外部方法调用时,能够将其做为方法的实参传递给方法的形参,并在方法内部回调此匿名函数,且回调此匿名函数时能够传递实参给到匿名函数的内部去接收,并执行此匿名函数。git
同时,此代码块或匿名函数也能够赋值给一个变量,使其具备自执行的能力,且最后一行的执行语句做为匿名函数的返回。github
可能乍一读并不太可以理解这段话的含义,能够先看几个例子:web
def b1 = { println "hello world" } b1() //此处也能够写成b1.call()
运行后,输出结果为:api
hello world
能够看出,所谓闭包,其实就是一段代码块(用大括号包括起来了)。它能够被赋值给一个闭包对象,若是查看此时b1的类型,实际上是Closure
。bash
既然存在闭包对象,那么很显然,也能够将其做为方法的实参传递给某个方法。例如:闭包
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进行循环,应该是最多见的一种操做了。闭包对此提供了三种方式:
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}}
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' }
在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 }