26. Groovy 闭包-语法与参数学习-第一篇

2023-02-23 17:40:26 浏览数 (2)

1. 介绍

本篇内容为Groovy学习笔记第26篇。相关知识为Groovy中的闭包使用。Groovy中的闭包是一个开放的、匿名的代码块,它可以接受参数、返回值并被分配给变量。闭包可以引用在其周围作用域中声明的变量。与闭包的正式定义相反,Groovy语言中的闭包还可以包含在其周围作用域之外定义的自由变量。虽然打破了闭包的正式概念,但它提供了本章所述的各种优点。

下面进入闭包(Closures)的详细讲解。

通过本篇介绍,将会让我们明白如何创建闭包,如何传参,以及一些基本的使用。

2. 语法-Syntax

闭包的创建语法遵循以下规范:

代码语言:javascript复制
{ [closureParameters -> ] statements }

其中[closureParameters->]是一个逗号分隔的可选参数列表,语句是0条或更多Groovy语句。参数看起来类似于方法参数列表,这些参数可以是类型化的或非类型化的。

当指定一个参数列表时,->字符是必需的,用于将实参从闭包体中分离出来。语句部分由0、1或许多Groovy语句组成。

下面是一些闭包的创建实例:

代码语言:javascript复制
//一个引用名为item的变量的闭包
{ item   }                                          
//可以通过添加箭头(->)显式地将闭包参数从代码中分离出来。
{ -> item   }                                       

//使用隐式参数(it)的闭包
{ println it }          

//它是一个显式参数的替代版本
{ it -> println it }               

//在这种情况下,通常最好为参数使用显式名称
{ name -> println name }                            

//接受两个类型化参数的闭包
{ String x, int y ->                                
    println "hey ${x} the value is ${y}"
}

//闭包可以包含多条语句
{ reader ->                                         
    def line = reader.readLine()
    line.trim()
}

PS:现在的各种高级语言中,都有闭包的概念和相关语法。整体实现逻辑都是差不多的。如果是第一次接触闭包这个概念,可能理解还是会有一些困难。

2.1 闭包作为对象使用

闭包是groovy.lang.Closure类的一个实例,使得它可以像任何其他变量一样被赋值给变量或字段,尽管它是一个代码块:

代码语言:javascript复制
//创建了一个闭包对象,并赋值给了listener
def listener = { 
    e -> println "Clicked on $e.source"
}
println(listener instanceof Closure) // 输出: true

Closure callback = { println 'Done!' }                      
Closure<Boolean> isTextFile = {
    File it -> it.name.endsWith('.txt')                     
}

2.2 调用闭包

闭包作为匿名代码块,可以像调用任何其他方法一样调用。如果你像这样定义一个不带参数的闭包:

代码语言:javascript复制
def code = { 'zinyan.com' }

然后闭包内部的代码只会在你调用闭包时被执行,这可以通过使用变量来完成,就像它是一个常规方法一样:

代码语言:javascript复制
println code() //输出: zinyan.com

上面的调用是隐式的调用方式,我们也可以显式调用:

代码语言:javascript复制
println code.call() // 输出: zinyan.com

如果闭包接受实参,原理是一样的:

代码语言:javascript复制
//创建一个闭包对象,并添加到isOdd引用。该闭包对象接受int的入参。
//将入参进行取余计算并判断是否能为0 ,其实就是判断是否为偶数。不是偶数返回true,是返回false
def isOdd = { int i -> i%2 != 0 }   

println isOdd(3) // 输出: true
println isOdd.call(2)   //输出 :false                      
                           
//创建一个闭包对象,采用隐式声明,也是一个int对象。判断是否为偶数是就返回true,否则返回false
def isEven = { it%2 == 0 }  
println isEven(3) // 输出:false
println isEven.call(2) // 输出:true                                

与方法不同,闭包在被调用时总是返回一个值。

PS: Groovy中方法的返回值,可以不用使用return关键字,命令行最后一行输出的内容就会自动默认为返回值了。

3. 参数-Parameters

上面也有介绍过闭包的两种参数,一种为正常的参数定义,一种是隐式参数。还有一种为可变参数(Varargs)。

3.1 正常参数

闭包的参数与常规方法的参数遵循相同的原理:

  • 可选类型。
  • 一个名字。
  • 可选的默认值。

参数之间也是使用逗号进行分割。示例如下:

代码语言:javascript复制
//创建一个闭包对象,将传入的值字母转换为大写并返回
def closureWithOneArg = { str -> str.toUpperCase() }
println closureWithOneArg('https://zinyan.com') //输出:HTTPS://ZINYAN.COM
//因为int数据类型没有toUpperCase方法。
// println closureWithOneArg(12345678) //输出: groovy.lang.MissingMethodException: No signature of method: java.lang.Integer.toUpperCase() 


//创建一个闭包对象,必须传入String字符,并将该字符进行大小写转换后返回
def closureWithOneArgAndExplicitType = { String str -> str.toUpperCase() }
println closureWithOneArgAndExplicitType('zinyan.com 1024') //输出:ZINYAN.COM 1024

//创建一个闭包对象,并将两个值进行相加然后返回结果。
def closureWithTwoArgs = { a,b -> a b }
println closureWithTwoArgs(2,4) //输出6
println closureWithTwoArgs(2,'z') //输出:2z  因为字符串对象也可以使用 号所以不会错

//创建一个闭包对象,将两个int值传入,并返回两个值的相加后的结果
def closureWithTwoArgsAndExplicitTypes = { int a, int b -> a b }
println closureWithTwoArgsAndExplicitTypes(1,3) //输出:4


//创建一个闭包对象,传入两个值,一个隐式类型,一个显式类型。并返回两者相加后的结果
def closureWithTwoArgsAndOptionalTypes = { a, int b -> a b }
println(closureWithTwoArgsAndOptionalTypes(1024,1024)) //输出:2048

//创建一个闭包对象,传入两个int值,其中一个值默认值为2(也就是不传值时,默认为2)。并返回两者相加后的结果。
def closureWithTwoArgAndDefaultValue = { int a, int b=2 -> a b }
println(closureWithTwoArgAndDefaultValue(1022)) //输出:1024

很多的概念和方法, 通过文字介绍会不清晰。通过上面的代码示例,就能够了解正常的参数创建了。

大部分情况下闭包的参数也是使用上面的介绍的情况进行传值的。

3.2 隐式参数

当闭包没有显式定义形参列表(使用->)时,闭包总是定义一个隐式形参,命名为it

简单理解就是,我们如果没有创建一个变量并给它命名的话。Groovy会默认给它一个名称:it。示例如下:

代码语言:javascript复制
//创建一个闭包对象, 有一个默认传参
def greeting = { "Hello, $it!" }

println greeting('zinyan.com') //输出;Hello, zinyan.com!

上面的隐式传参,和下面是等效的:

代码语言:javascript复制
def greeting = { it -> "Hello, $it!" }

println greeting('zinyan.com') //输出:Hello, zinyan.com!

如果想声明一个闭包,它不接受参数,并且必须被限制为不带参数的调用,那么你必须用一个显式的空参数列表来声明它:

代码语言:javascript复制
//创建一个闭包对象,不允许传参
def magicNumber = { -> 1024 }

println magicNumber() //输出: 1024
//下面这个写法就会出现错误了。
println magicNumber(11)//groovy.lang.MissingMethodException: No signature of method: zinyan$_run_closure1.call() is applicable for argument types:

3.3 可变参数

闭包可以像其他方法一样声明变量参数。如果最后一个形参是可变长度的(或数组),就可以接受可变数量的实参,就像下面的例子:

代码语言:javascript复制
//创建一个可变String 参数的闭包对象。并将该数组中的所有元素放入一个字符串中进行返回。
def concat1 = { String... args -> args.join('') }           
println concat1('zin','yan','.com') // 输出:zinyan.com

//创建一个数组对象入参,并将该数组中的所有元素放入一个字符串中进行返回。
def concat2 = { String[] args -> args.join('') }       
println concat2('z','i','n','y','a','n') //输出:zinyan

//创建一个int入参和一个String可变入参。 将返回的字符串进行翻倍输出
def multiConcat = { int n, String... args ->                
    args.join('')*n
}
println multiConcat(3,'zin','yan','.com') // 输出:zinyan.comzinyan.comzinyan.com

通过上面的示例就可以看到。只要最后一个入参是数组或显式vargs类型(使用关键字:...)那么就可以当做可变传参使用。

整个的创建过程和普通方法中的可变传参是一样的。

4. 小结

本篇知识介绍了闭包的基本语法和参数。以及闭包的使用。明白上面的内容之后,可以说闭包的使用我们就学会了。

大部分情况下使用闭包我们也使用到这种程度就差不多了。

后面将会深入的介绍闭包的其他特性和使用场景。请期待Groovy闭包知识第二篇。

本篇内容参考来源于Groovy官方文档:http://docs.groovy-lang.org/docs/groovy-4.0.6/html/documentation/#_closures

如果觉得内容写的还不错,希望能够给我点个赞。谢谢!

0 人点赞